The Battle for Wesnoth  1.19.2+dev
text.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2024
3  by Mark de Wever <koraq@xs4all.nl>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #define GETTEXT_DOMAIN "wesnoth-lib"
17 
18 #include "font/text.hpp"
19 
20 #include "font/font_config.hpp"
21 
22 #include "font/pango/escape.hpp"
23 #include "font/pango/font.hpp"
24 #include "font/pango/hyperlink.hpp"
26 
27 #include "gettext.hpp"
28 #include "gui/widgets/helper.hpp"
29 #include "gui/core/log.hpp"
30 #include "sdl/point.hpp"
33 #include "video.hpp"
34 
35 
36 #include <cassert>
37 #include <cstring>
38 #include <stdexcept>
39 
40 static lg::log_domain log_font("font");
41 #define DBG_FT LOG_STREAM(debug, log_font)
42 
43 namespace font
44 {
45 
46 namespace
47 {
48 /**
49  * The text texture cache.
50  *
51  * Each time a specific bit of text is rendered, a corresponding texture is created and
52  * added to the cache. We don't store the surface since there isn't really any use for
53  * it. If we need texture size that can be easily queried.
54  *
55  * @todo Figure out how this can be optimized with a texture atlas. It should be possible
56  * to store smaller bits of text in the atlas and construct new textures from hem.
57  */
58 std::map<std::size_t, texture> rendered_cache{};
59 } // anon namespace
60 
62 {
63  rendered_cache.clear();
64 }
65 
67  : context_(pango_font_map_create_context(pango_cairo_font_map_get_default()), g_object_unref)
68  , layout_(pango_layout_new(context_.get()), g_object_unref)
69  , rect_()
70  , text_()
71  , markedup_text_(false)
72  , link_aware_(false)
73  , link_color_()
74  , font_class_(font::FONT_SANS_SERIF)
75  , font_size_(14)
76  , font_style_(STYLE_NORMAL)
77  , foreground_color_() // solid white
78  , add_outline_(false)
79  , maximum_width_(-1)
80  , characters_per_line_(0)
81  , maximum_height_(-1)
82  , ellipse_mode_(PANGO_ELLIPSIZE_END)
83  , alignment_(PANGO_ALIGN_LEFT)
84  , maximum_length_(std::string::npos)
85  , calculation_dirty_(true)
86  , length_(0)
87  , attribute_start_offset_(0)
88  , attribute_end_offset_(0)
89  , highlight_color_()
90  , attrib_hash_(0)
91  , pixel_scale_(1)
92  , surface_buffer_()
93 {
94  // Initialize global list
95  global_attribute_list_ = pango_attr_list_new();
96 
97  // With 72 dpi the sizes are the same as with SDL_TTF so hardcoded.
98  pango_cairo_context_set_resolution(context_.get(), 72.0);
99 
100  pango_layout_set_ellipsize(layout_.get(), ellipse_mode_);
101  pango_layout_set_alignment(layout_.get(), alignment_);
102  pango_layout_set_wrap(layout_.get(), PANGO_WRAP_WORD_CHAR);
103  pango_layout_set_line_spacing(layout_.get(), get_line_spacing_factor());
104 
105  cairo_font_options_t *fo = cairo_font_options_create();
106  cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL);
107  cairo_font_options_set_hint_metrics(fo, CAIRO_HINT_METRICS_ON);
108  cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_DEFAULT);
109 
110  pango_cairo_context_set_font_options(context_.get(), fo);
111  cairo_font_options_destroy(fo);
112 }
113 
114 texture pango_text::render_texture(const SDL_Rect& viewport)
115 {
116  return with_draw_scale(texture(render_surface(viewport)));
117 }
118 
120 {
121  // Update our settings then hash them.
122  update_pixel_scale(); // TODO: this should be in recalculate()
123  recalculate();
124  const std::size_t hash = std::hash<pango_text>{}(*this);
125  // If we already have the appropriate texture in-cache, use it.
126  if(const auto iter = rendered_cache.find(hash); iter != rendered_cache.end()) {
127  return with_draw_scale(iter->second);
128  }
129 
130  if(surface text_surf = create_surface(); text_surf) {
131  const auto& [new_iter, added] = rendered_cache.try_emplace(hash, std::move(text_surf));
132  return with_draw_scale(new_iter->second);
133  }
134 
135  // Render output was null for some reason. Don't cache.
136  return {};
137 }
138 
139 surface pango_text::render_surface(const SDL_Rect& viewport)
140 {
141  update_pixel_scale(); // TODO: this should be in recalculate()
142  recalculate();
143  return create_surface(viewport);
144 }
145 
147 {
148  texture res(t);
149  res.set_draw_size(to_draw_scale(t.get_raw_size()));
150  return res;
151 }
152 
154 {
155  return (i + pixel_scale_ - 1) / pixel_scale_;
156 }
157 
159 {
160  // Round up, rather than truncating.
161  return {to_draw_scale(p.x), to_draw_scale(p.y)};
162 }
163 
165 {
166  update_pixel_scale(); // TODO: this should be in recalculate()
167  this->recalculate();
168 
169  return to_draw_scale({rect_.width, rect_.height});
170 }
171 
173 {
174  this->recalculate();
175 
176  return (pango_layout_is_ellipsized(layout_.get()) != 0);
177 }
178 
179 unsigned pango_text::insert_text(const unsigned offset, const std::string& text)
180 {
181  if (text.empty() || length_ == maximum_length_) {
182  return 0;
183  }
184 
185  // do we really need that assert? utf8::insert will just append in this case, which seems fine
186  assert(offset <= length_);
187 
188  unsigned len = utf8::size(text);
189  if (length_ + len > maximum_length_) {
190  len = maximum_length_ - length_;
191  }
192  const std::string insert = text.substr(0, utf8::index(text, len));
193  std::string tmp = text_;
194  this->set_text(utf8::insert(tmp, offset, insert), false);
195  // report back how many characters were actually inserted (e.g. to move the cursor selection)
196  return len;
197 }
198 
199 int pango_text::get_byte_offset(const unsigned column) const
200 {
201  // First we need to determine the byte offset
202  std::unique_ptr<PangoLayoutIter, std::function<void(PangoLayoutIter*)>> itor(
203  pango_layout_get_iter(layout_.get()), pango_layout_iter_free);
204 
205  // Go the wanted column.
206  for(std::size_t i = 0; i < column; ++i) {
207  if(!pango_layout_iter_next_char(itor.get())) {
208  // It seems that the documentation is wrong and causes and off by
209  // one error... the result should be false if already at the end of
210  // the data when started.
211  if(i + 1 == column) {
212  break;
213  }
214  }
215  }
216 
217  // Get the byte offset
218  const int offset = pango_layout_iter_get_index(itor.get());
219  return offset;
220 }
221 
222 point pango_text::get_cursor_position(const unsigned column, const unsigned line) const
223 {
224  this->recalculate();
225 
226  // Determing byte offset
227  std::unique_ptr<PangoLayoutIter, std::function<void(PangoLayoutIter*)>> itor(
228  pango_layout_get_iter(layout_.get()), pango_layout_iter_free);
229 
230  // Go the wanted line.
231  if(line != 0) {
232 
233  if(static_cast<int>(line) >= pango_layout_get_line_count(layout_.get())) {
234  return point(0, 0);
235  }
236 
237  for(std::size_t i = 0; i < line; ++i) {
238  pango_layout_iter_next_line(itor.get());
239  }
240  }
241 
242  // Go the wanted column.
243  for(std::size_t i = 0; i < column; ++i) {
244  if(!pango_layout_iter_next_char(itor.get())) {
245  // It seems that the documentation is wrong and causes and off by
246  // one error... the result should be false if already at the end of
247  // the data when started.
248  if(i + 1 == column) {
249  break;
250  }
251  // Beyond data.
252  return point(0, 0);
253  }
254  }
255 
256  // Get the byte offset
257  const int offset = pango_layout_iter_get_index(itor.get());
258 
259  // Convert the byte offset in a position.
260  PangoRectangle rect;
261  pango_layout_get_cursor_pos(layout_.get(), offset, &rect, nullptr);
262 
263  return to_draw_scale({PANGO_PIXELS(rect.x), PANGO_PIXELS(rect.y)});
264 }
265 
267 {
268  return maximum_length_;
269 }
270 
271 std::string pango_text::get_token(const point & position, const char * delim) const
272 {
273  this->recalculate();
274 
275  // Get the index of the character.
276  int index, trailing;
277  if (!pango_layout_xy_to_index(layout_.get(), position.x * PANGO_SCALE,
278  position.y * PANGO_SCALE, &index, &trailing)) {
279  return "";
280  }
281 
282  std::string txt = pango_layout_get_text(layout_.get());
283 
284  std::string d(delim);
285 
286  if (index < 0 || (static_cast<std::size_t>(index) >= txt.size()) || d.find(txt.at(index)) != std::string::npos) {
287  return ""; // if the index is out of bounds, or the index character is a delimiter, return nothing
288  }
289 
290  std::size_t l = index;
291  while (l > 0 && (d.find(txt.at(l-1)) == std::string::npos)) {
292  --l;
293  }
294 
295  std::size_t r = index + 1;
296  while (r < txt.size() && (d.find(txt.at(r)) == std::string::npos)) {
297  ++r;
298  }
299 
300  return txt.substr(l,r-l);
301 }
302 
303 std::string pango_text::get_link(const point & position) const
304 {
305  if (!link_aware_) {
306  return "";
307  }
308 
309  std::string tok = this->get_token(position, " \n\r\t");
310 
311  if (looks_like_url(tok)) {
312  return tok;
313  } else {
314  return "";
315  }
316 }
317 
319 {
320  this->recalculate();
321 
322  // Get the index of the character.
323  int index, trailing;
324  pango_layout_xy_to_index(layout_.get(), position.x * PANGO_SCALE,
325  position.y * PANGO_SCALE, &index, &trailing);
326 
327  // Extract the line and the offset in pixels in that line.
328  int line, offset;
329  pango_layout_index_to_line_x(layout_.get(), index, trailing, &line, &offset);
330  offset = PANGO_PIXELS(offset);
331 
332  // Now convert this offset to a column, this way is a bit hacky but haven't
333  // found a better solution yet.
334 
335  /**
336  * @todo There's still a bug left. When you select a text which is in the
337  * ellipses on the right side the text gets reformatted with ellipses on
338  * the left and the selected character is not the one under the cursor.
339  * Other widget toolkits don't show ellipses and have no indication more
340  * text is available. Haven't found what the best thing to do would be.
341  * Until that time leave it as is.
342  */
343  for(std::size_t i = 0; ; ++i) {
344  const int pos = this->get_cursor_position(i, line).x;
345 
346  if(pos == offset) {
347  return point(i, line);
348  }
349  }
350 }
351 
352 void pango_text::add_attribute_size(const unsigned start_offset, const unsigned end_offset, int size)
353 {
354  attribute_start_offset_ = start_offset;
355  attribute_end_offset_ = end_offset;
356 
358  PangoAttribute *attr = pango_attr_size_new_absolute(PANGO_SCALE * size);
359  attr->start_index = attribute_start_offset_;
360  attr->end_index = attribute_end_offset_;
361 
362  DBG_GUI_D << "attribute: size";
363  DBG_GUI_D << "attribute start: " << start_offset << " end : " << end_offset;
364 
365  // Update hash
366  boost::hash_combine(attrib_hash_, attribute_start_offset_);
367  boost::hash_combine(attrib_hash_, attribute_end_offset_);
368  boost::hash_combine(attrib_hash_, size);
369 
370  // Insert all attributes
371  pango_attr_list_insert(global_attribute_list_, attr);
372  }
373 }
374 
375 void pango_text::add_attribute_weight(const unsigned start_offset, const unsigned end_offset, PangoWeight weight)
376 {
377  attribute_start_offset_ = start_offset;
378  attribute_end_offset_ = end_offset;
379 
381  PangoAttribute *attr = pango_attr_weight_new(weight);
382  attr->start_index = attribute_start_offset_;
383  attr->end_index = attribute_end_offset_;
384 
385  DBG_GUI_D << "attribute: weight";
386  DBG_GUI_D << "attribute start: " << start_offset << " end : " << end_offset;
387 
388  // Update hash
389  boost::hash_combine(attrib_hash_, attribute_start_offset_);
390  boost::hash_combine(attrib_hash_, attribute_end_offset_);
391  boost::hash_combine(attrib_hash_, weight);
392 
393  // Insert all attributes
394  pango_attr_list_insert(global_attribute_list_, attr);
395  }
396 }
397 
398 void pango_text::add_attribute_style(const unsigned start_offset, const unsigned end_offset, PangoStyle style)
399 {
400  attribute_start_offset_ = start_offset;
401  attribute_end_offset_ = end_offset;
402 
404 
405  PangoAttribute *attr = pango_attr_style_new(style);
406  attr->start_index = attribute_start_offset_;
407  attr->end_index = attribute_end_offset_;
408 
409  DBG_GUI_D << "attribute: style";
410  DBG_GUI_D << "attribute start: " << attribute_start_offset_ << " end : " << attribute_end_offset_;
411 
412  // Update hash
413  boost::hash_combine(attrib_hash_, attribute_start_offset_);
414  boost::hash_combine(attrib_hash_, attribute_end_offset_);
415 
416  // Insert all attributes
417  pango_attr_list_insert(global_attribute_list_, attr);
418  }
419 }
420 
421 void pango_text::add_attribute_underline(const unsigned start_offset, const unsigned end_offset, PangoUnderline underline)
422 {
423  attribute_start_offset_ = start_offset;
424  attribute_end_offset_ = end_offset;
425 
427  PangoAttribute *attr = pango_attr_underline_new(underline);
428  attr->start_index = attribute_start_offset_;
429  attr->end_index = attribute_end_offset_;
430 
431  DBG_GUI_D << "attribute: underline";
432  DBG_GUI_D << "attribute start: " << start_offset << " end : " << end_offset;
433 
434  // Update hash
435  boost::hash_combine(attrib_hash_, attribute_start_offset_);
436  boost::hash_combine(attrib_hash_, attribute_end_offset_);
437  boost::hash_combine(attrib_hash_, underline);
438 
439  // Insert all attributes
440  pango_attr_list_insert(global_attribute_list_, attr);
441  }
442 }
443 
444 
445 void pango_text::add_attribute_fg_color(const unsigned start_offset, const unsigned end_offset, const color_t& color)
446 {
447  attribute_start_offset_ = start_offset;
448  attribute_end_offset_ = end_offset;
449 
451  int col_r = color.r / 255.0 * 65535.0;
452  int col_g = color.g / 255.0 * 65535.0;
453  int col_b = color.b / 255.0 * 65535.0;
454 
455  PangoAttribute *attr = pango_attr_foreground_new(col_r, col_g, col_b);
456  attr->start_index = start_offset;
457  attr->end_index = end_offset;
458 
459  DBG_GUI_D << "attribute: fg color";
460  DBG_GUI_D << "attribute start: " << attribute_start_offset_ << " end : " << attribute_end_offset_;
461  DBG_GUI_D << "color: " << col_r << "," << col_g << "," << col_b;
462 
463  // Update hash
464  boost::hash_combine(attrib_hash_, attribute_start_offset_);
465  boost::hash_combine(attrib_hash_, attribute_end_offset_);
466  boost::hash_combine(attrib_hash_, color.to_rgba_bytes());
467 
468  // Insert all attributes
469  pango_attr_list_insert(global_attribute_list_, attr);
470  }
471 }
472 
473 void pango_text::add_attribute_font_family(const unsigned start_offset, const unsigned end_offset, std::string family)
474 {
475  attribute_start_offset_ = start_offset;
476  attribute_end_offset_ = end_offset;
477 
479  PangoAttribute *attr = pango_attr_family_new(family.c_str());
480  attr->start_index = attribute_start_offset_;
481  attr->end_index = attribute_end_offset_;
482 
483  DBG_GUI_D << "attribute: font family";
484  DBG_GUI_D << "attribute start: " << start_offset << " end : " << end_offset;
485  DBG_GUI_D << "font family: " << family;
486 
487  // Update hash
488  boost::hash_combine(attrib_hash_, attribute_start_offset_);
489  boost::hash_combine(attrib_hash_, attribute_end_offset_);
490  boost::hash_combine(attrib_hash_, family);
491 
492  // Insert all attributes
493  pango_attr_list_insert(global_attribute_list_, attr);
494  }
495 }
496 
497 void pango_text::set_highlight_area(const unsigned start_offset, const unsigned end_offset, const color_t& color) {
498  attribute_start_offset_ = start_offset;
499  attribute_end_offset_ = end_offset;
500  highlight_color_ = color;
501 
503  // Highlight
504  int col_r = highlight_color_.r / 255.0 * 65535.0;
505  int col_g = highlight_color_.g / 255.0 * 65535.0;
506  int col_b = highlight_color_.b / 255.0 * 65535.0;
507 
508  DBG_GUI_D << "highlight start: " << attribute_start_offset_ << "end : " << attribute_end_offset_;
509  DBG_GUI_D << "highlight color: " << col_r << "," << col_g << "," << col_b;
510 
511  PangoAttribute *attr = pango_attr_background_new(col_r, col_g, col_b);
512  attr->start_index = attribute_start_offset_;
513  attr->end_index = attribute_end_offset_;
514 
515  // Update hash
516  boost::hash_combine(attrib_hash_, attribute_start_offset_);
517  boost::hash_combine(attrib_hash_, attribute_end_offset_);
518  boost::hash_combine(attrib_hash_, highlight_color_.to_rgba_bytes());
519 
520  // Insert all attributes
521  pango_attr_list_insert(global_attribute_list_, attr);
522  }
523 }
524 
526  global_attribute_list_ = pango_attr_list_new();
527  pango_layout_set_attributes(layout_.get(), global_attribute_list_);
528 }
529 
530 bool pango_text::set_text(const std::string& text, const bool markedup)
531 {
532  if(markedup != markedup_text_ || text != text_) {
533  if(layout_ == nullptr) {
534  layout_.reset(pango_layout_new(context_.get()));
535  }
536 
537  const std::u32string wide = unicode_cast<std::u32string>(text);
538  const std::string narrow = unicode_cast<std::string>(wide);
539  if(text != narrow) {
540  ERR_GUI_L << "pango_text::" << __func__
541  << " text '" << text
542  << "' contains invalid utf-8, trimmed the invalid parts.";
543  }
544 
545  pango_layout_set_attributes(layout_.get(), global_attribute_list_);
546  // Clear list. Using pango_attr_list_unref() causes segfault
547  global_attribute_list_ = pango_attr_list_new();
548 
549  if(markedup) {
550  if(!this->set_markup(narrow, *layout_)) {
551  return false;
552  }
553  } else {
555  /*
556  * pango_layout_set_text after pango_layout_set_markup might
557  * leave the layout in an undefined state regarding markup so
558  * clear it unconditionally.
559  */
560  pango_layout_set_attributes(layout_.get(), nullptr);
561  }
562 
563  pango_layout_set_text(layout_.get(), narrow.c_str(), narrow.size());
564  }
565 
566  text_ = narrow;
567  length_ = wide.size();
568  markedup_text_ = markedup;
569  calculation_dirty_ = true;
570  }
571 
572  return true;
573 }
574 
576 {
577  if(fclass != font_class_) {
578  font_class_ = fclass;
579  calculation_dirty_ = true;
580  }
581 
582  return *this;
583 }
584 
586 {
587  font_size = prefs::get().font_scaled(font_size) * pixel_scale_;
588 
589  if(font_size != font_size_) {
590  font_size_ = font_size;
591  calculation_dirty_ = true;
592  }
593 
594  return *this;
595 }
596 
598 {
599  if(font_style != font_style_) {
600  font_style_ = font_style;
601  calculation_dirty_ = true;
602  }
603 
604  return *this;
605 }
606 
608 {
609  if(color != foreground_color_) {
610  foreground_color_ = color;
611  }
612 
613  return *this;
614 }
615 
617 {
618  width *= pixel_scale_;
619 
620  if(width <= 0) {
621  width = -1;
622  }
623 
624  if(width != maximum_width_) {
625  maximum_width_ = width;
626  calculation_dirty_ = true;
627  }
628 
629  return *this;
630 }
631 
632 pango_text& pango_text::set_characters_per_line(const unsigned characters_per_line)
633 {
634  if(characters_per_line != characters_per_line_) {
635  characters_per_line_ = characters_per_line;
636 
637  calculation_dirty_ = true;
638  }
639 
640  return *this;
641 }
642 
643 pango_text& pango_text::set_maximum_height(int height, bool multiline)
644 {
645  height *= pixel_scale_;
646 
647  if(height <= 0) {
648  height = -1;
649  multiline = false;
650  }
651 
652  if(height != maximum_height_) {
653  // assert(context_);
654 
655  // The maximum height is handled in this class' calculate_size() method.
656  //
657  // Although we also pass it to PangoLayout if multiline is true, the documentation of pango_layout_set_height
658  // makes me wonder whether we should avoid that function completely. For example, "at least one line is included
659  // in each paragraph regardless" and "may be changed in future, file a bug if you rely on the current behavior".
660  pango_layout_set_height(layout_.get(), !multiline ? -1 : height * PANGO_SCALE);
661  maximum_height_ = height;
662  calculation_dirty_ = true;
663  }
664 
665  return *this;
666 }
667 
668 pango_text& pango_text::set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
669 {
670  if(ellipse_mode != ellipse_mode_) {
671  // assert(context_);
672 
673  pango_layout_set_ellipsize(layout_.get(), ellipse_mode);
674  ellipse_mode_ = ellipse_mode;
675  calculation_dirty_ = true;
676  }
677 
678  // According to the docs of pango_layout_set_height, the behavior is undefined if a height other than -1 is combined
679  // with PANGO_ELLIPSIZE_NONE. Wesnoth's code currently always calls set_ellipse_mode after set_maximum_height, so do
680  // the cleanup here. The code in calculate_size() will still apply the maximum height after Pango's calculations.
681  if(ellipse_mode_ == PANGO_ELLIPSIZE_NONE) {
682  pango_layout_set_height(layout_.get(), -1);
683  }
684 
685  return *this;
686 }
687 
688 pango_text &pango_text::set_alignment(const PangoAlignment alignment)
689 {
690  if (alignment != alignment_) {
691  pango_layout_set_alignment(layout_.get(), alignment);
692  alignment_ = alignment;
693  }
694 
695  return *this;
696 }
697 
698 pango_text& pango_text::set_maximum_length(const std::size_t maximum_length)
699 {
700  if(maximum_length != maximum_length_) {
701  maximum_length_ = maximum_length;
702  if(length_ > maximum_length_) {
703  std::string tmp = text_;
704  this->set_text(utf8::truncate(tmp, maximum_length_), false);
705  }
706  }
707 
708  return *this;
709 }
710 
712 {
713  if (link_aware_ != b) {
714  calculation_dirty_ = true;
715  link_aware_ = b;
716  }
717  return *this;
718 }
719 
721 {
722  if(color != link_color_) {
723  link_color_ = color;
724  calculation_dirty_ = true;
725  }
726 
727  return *this;
728 }
729 
731 {
732  if(do_add != add_outline_) {
733  add_outline_ = do_add;
734  //calculation_dirty_ = true;
735  }
736 
737  return *this;
738 }
739 
741 {
743 
744  PangoFont* f = pango_font_map_load_font(
745  pango_cairo_font_map_get_default(),
746  context_.get(),
747  font.get());
748 
749  PangoFontMetrics* m = pango_font_get_metrics(f, nullptr);
750 
751  auto ascent = pango_font_metrics_get_ascent(m);
752  auto descent = pango_font_metrics_get_descent(m);
753 
754  pango_font_metrics_unref(m);
755  g_object_unref(f);
756 
757  return ceil(pango_units_to_double(ascent + descent) / pixel_scale_);
758 }
759 
761 {
762  const int ps = video::get_pixel_scale();
763  if (ps == pixel_scale_) {
764  return;
765  }
766 
768 
769  if (maximum_width_ != -1) {
771  }
772 
773  if (maximum_height_ != -1) {
775  }
776 
777  calculation_dirty_ = true;
778  pixel_scale_ = ps;
779 }
780 
782 {
783  // TODO: clean up this "const everything then mutable everything" mess.
784  // update_pixel_scale() should go in here. But it can't. Because things
785  // are declared const which are not const.
786 
787  if(calculation_dirty_) {
788  assert(layout_ != nullptr);
789 
790  calculation_dirty_ = false;
792  }
793 }
794 
795 PangoRectangle pango_text::calculate_size(PangoLayout& layout) const
796 {
797  PangoRectangle size;
798 
800  pango_layout_set_font_description(&layout, font.get());
801 
803  PangoAttrList *attribute_list = pango_attr_list_new();
804  pango_attr_list_insert(attribute_list
805  , pango_attr_underline_new(PANGO_UNDERLINE_SINGLE));
806 
807  pango_layout_set_attributes(&layout, attribute_list);
808  pango_attr_list_unref(attribute_list);
809  }
810 
811  int maximum_width = 0;
812  if(characters_per_line_ != 0) {
813  PangoFont* f = pango_font_map_load_font(
814  pango_cairo_font_map_get_default(),
815  context_.get(),
816  font.get());
817 
818  PangoFontMetrics* m = pango_font_get_metrics(f, nullptr);
819 
820  int w = pango_font_metrics_get_approximate_char_width(m);
822 
823  maximum_width = ceil(pango_units_to_double(w));
824 
825  pango_font_metrics_unref(m);
826  g_object_unref(f);
827  } else {
828  maximum_width = maximum_width_;
829  }
830 
831  if(maximum_width_ != -1) {
832  maximum_width = std::min(maximum_width, maximum_width_);
833  }
834 
835  pango_layout_set_width(&layout, maximum_width == -1
836  ? -1
837  : maximum_width * PANGO_SCALE);
838  pango_layout_get_pixel_extents(&layout, nullptr, &size);
839 
840  DBG_GUI_L << "pango_text::" << __func__
841  << " text '" << gui2::debug_truncate(text_)
842  << "' maximum_width " << maximum_width
843  << " width " << size.x + size.width
844  << ".";
845 
846  DBG_GUI_L << "pango_text::" << __func__
847  << " text '" << gui2::debug_truncate(text_)
848  << "' font_size " << font_size_
849  << " markedup_text " << markedup_text_
850  << " font_style " << std::hex << font_style_ << std::dec
851  << " maximum_width " << maximum_width
852  << " maximum_height " << maximum_height_
853  << " result " << size
854  << ".";
855 
856  if(maximum_width != -1 && size.x + size.width > maximum_width) {
857  DBG_GUI_L << "pango_text::" << __func__
858  << " text '" << gui2::debug_truncate(text_)
859  << " ' width " << size.x + size.width
860  << " greater as the wanted maximum of " << maximum_width
861  << ".";
862  }
863 
864  // The maximum height is handled here instead of using the library - see the comments in set_maximum_height()
865  if(maximum_height_ != -1 && size.y + size.height > maximum_height_) {
866  DBG_GUI_L << "pango_text::" << __func__
867  << " text '" << gui2::debug_truncate(text_)
868  << " ' height " << size.y + size.height
869  << " greater as the wanted maximum of " << maximum_height_
870  << ".";
871  size.height = maximum_height_ - std::max(0, size.y);
872  }
873 
874  return size;
875 }
876 
877 /***
878  * Inverse table
879  *
880  * Holds a high-precision inverse for each number i, that is, a number x such that x * i / 256 is close to 255.
881  */
883 {
884  unsigned values[256] {};
885 
886  constexpr inverse_table()
887  {
888  values[0] = 0;
889  for (int i = 1; i < 256; ++i) {
890  values[i] = (255 * 256) / i;
891  }
892  }
893 
894  unsigned operator[](uint8_t i) const { return values[i]; }
895 };
896 
897 static constexpr inverse_table inverse_table_;
898 
899 /***
900  * Helper function for un-premultiplying alpha
901  * Div should be the high-precision inverse for the alpha value.
902  */
903 static void unpremultiply(uint8_t & value, const unsigned div) {
904  unsigned temp = (value * div) / 256u;
905  // Note: It's always the case that alpha * div < 256 if div is the inverse
906  // for alpha, so if cairo is computing premultiplied alpha by rounding down,
907  // this min is not necessary. However, if cairo generates illegal output,
908  // the min may be selected.
909  // It's probably not worth removing the min, since branch prediction will
910  // make it essentially free if one of the branches is never actually
911  // selected.
912  value = std::min(255u, temp);
913 }
914 
915 /**
916  * Converts from cairo-format ARGB32 premultiplied alpha to plain alpha.
917  * @param c a uint32 representing the color
918  */
919 static void from_cairo_format(uint32_t & c)
920 {
921  uint8_t a = (c >> 24) & 0xff;
922  uint8_t r = (c >> 16) & 0xff;
923  uint8_t g = (c >> 8) & 0xff;
924  uint8_t b = c & 0xff;
925 
926  const unsigned div = inverse_table_[a];
927  unpremultiply(r, div);
928  unpremultiply(g, div);
929  unpremultiply(b, div);
930 
931  c = (static_cast<uint32_t>(a) << 24) | (static_cast<uint32_t>(r) << 16) | (static_cast<uint32_t>(g) << 8) | static_cast<uint32_t>(b);
932 }
933 
934 void pango_text::render(PangoLayout& layout, const SDL_Rect& viewport, const unsigned stride)
935 {
936  cairo_format_t format = CAIRO_FORMAT_ARGB32;
937 
938  uint8_t* buffer = &surface_buffer_[0];
939 
940  std::unique_ptr<cairo_surface_t, std::function<void(cairo_surface_t*)>> cairo_surface(
941  cairo_image_surface_create_for_data(buffer, format, viewport.w, viewport.h, stride), cairo_surface_destroy);
942  std::unique_ptr<cairo_t, std::function<void(cairo_t*)>> cr(cairo_create(cairo_surface.get()), cairo_destroy);
943 
944  if(cairo_status(cr.get()) == CAIRO_STATUS_INVALID_SIZE) {
945  throw std::length_error("Text is too long to render");
946  }
947 
948  // The top-left of the text, which can be outside the area to be rendered
949  cairo_move_to(cr.get(), -viewport.x, -viewport.y);
950 
951  //
952  // TODO: the outline may be slightly cut off around certain text if it renders too
953  // close to the surface's edge. That causes the outline to extend just slightly
954  // outside the surface's borders. I'm not sure how best to deal with this. Obviously,
955  // we want to increase the surface size, but we also don't want to invalidate all
956  // the placement and size calculations. Thankfully, it's not very noticeable.
957  //
958  // -- vultraz, 2018-03-07
959  //
960  if(add_outline_) {
961  // Add a path to the cairo context tracing the current text.
962  pango_cairo_layout_path(cr.get(), &layout);
963 
964  // Set color for background outline (black).
965  cairo_set_source_rgba(cr.get(), 0.0, 0.0, 0.0, 1.0);
966 
967  cairo_set_line_join(cr.get(), CAIRO_LINE_JOIN_ROUND);
968  cairo_set_line_width(cr.get(), 3.0); // Adjust as necessary
969 
970  // Stroke path to draw outline.
971  cairo_stroke(cr.get());
972  }
973 
974  // Set main text color.
975  cairo_set_source_rgba(cr.get(),
976  foreground_color_.r / 255.0,
977  foreground_color_.g / 255.0,
978  foreground_color_.b / 255.0,
979  foreground_color_.a / 255.0
980  );
981 
982  pango_cairo_show_layout(cr.get(), &layout);
983 }
984 
986 {
987  return create_surface({0, 0, rect_.x + rect_.width, rect_.y + rect_.height});
988 }
989 
990 surface pango_text::create_surface(const SDL_Rect& viewport)
991 {
992  assert(layout_.get());
993 
994  cairo_format_t format = CAIRO_FORMAT_ARGB32;
995  const int stride = cairo_format_stride_for_width(format, viewport.w);
996 
997  // The width and stride can be zero if the text is empty or the stride can be negative to indicate an error from
998  // Cairo. Width isn't tested here because it's implied by stride.
999  if(stride <= 0 || viewport.h <= 0) {
1000  surface_buffer_.clear();
1001  return nullptr;
1002  }
1003 
1004  DBG_FT << "creating new text surface";
1005 
1006  // Check to prevent arithmetic overflow when calculating (stride * height).
1007  // The size of the viewport should already provide a far lower limit on the
1008  // maximum size, but this is left in as a sanity check.
1009  if(viewport.h > std::numeric_limits<int>::max() / stride) {
1010  throw std::length_error("Text is too long to render");
1011  }
1012 
1013  // Resize buffer appropriately and set all pixel values to 0.
1014  surface_buffer_.assign(viewport.h * stride, 0);
1015 
1016  // Try rendering the whole text in one go. If this throws a length_error
1017  // then leave it to the caller to handle; one reason it may throw is that
1018  // cairo surfaces are limited to approximately 2**15 pixels in height.
1019  render(*layout_, viewport, stride);
1020 
1021  // The cairo surface is in CAIRO_FORMAT_ARGB32 which uses
1022  // pre-multiplied alpha. SDL doesn't use that so the pixels need to be
1023  // decoded again.
1024  for(int y = 0; y < viewport.h; ++y) {
1025  uint32_t* pixels = reinterpret_cast<uint32_t*>(&surface_buffer_[y * stride]);
1026  for(int x = 0; x < viewport.w; ++x) {
1027  from_cairo_format(pixels[x]);
1028  }
1029  }
1030 
1031  return SDL_CreateRGBSurfaceWithFormatFrom(
1032  &surface_buffer_[0], viewport.w, viewport.h, 32, stride, SDL_PIXELFORMAT_ARGB8888);
1033 }
1034 
1035 bool pango_text::set_markup(std::string_view text, PangoLayout& layout)
1036 {
1037  char* raw_text;
1038  std::string semi_escaped;
1039  bool valid = validate_markup(text, &raw_text, semi_escaped);
1040  if(semi_escaped != "") {
1041  text = semi_escaped;
1042  }
1043 
1044  if(valid) {
1045  if(link_aware_) {
1046  std::string formatted_text = format_links(text);
1047  pango_layout_set_markup(&layout, formatted_text.c_str(), formatted_text.size());
1048  } else {
1049  pango_layout_set_markup(&layout, text.data(), text.size());
1050  }
1051  } else {
1052  ERR_GUI_L << "pango_text::" << __func__
1053  << " text '" << text
1054  << "' has broken markup, set to normal text.";
1055  set_text(_("The text contains invalid Pango markup: ") + std::string(text), false);
1056  }
1057 
1058  return valid;
1059 }
1060 
1061 /**
1062  * Replaces all instances of URLs in a given string with formatted links
1063  * and returns the result.
1064  */
1065 std::string pango_text::format_links(std::string_view text) const
1066 {
1067  static const std::string delim = " \n\r\t";
1068  std::ostringstream result;
1069 
1070  std::size_t tok_start = 0;
1071  for(std::size_t pos = 0; pos < text.length(); ++pos) {
1072  if(delim.find(text[pos]) == std::string::npos) {
1073  continue;
1074  }
1075 
1076  if(const auto tok_length = pos - tok_start) {
1077  // Token starts from after the last delimiter up to (but not including) this delimiter
1078  auto token = text.substr(tok_start, tok_length);
1079  if(looks_like_url(token)) {
1080  result << format_as_link(std::string{token}, link_color_);
1081  } else {
1082  result << token;
1083  }
1084  }
1085 
1086  result << text[pos];
1087  tok_start = pos + 1;
1088  }
1089 
1090  // Deal with the remainder token
1091  if(tok_start < text.length()) {
1092  auto token = text.substr(tok_start);
1093  if(looks_like_url(token)) {
1094  result << format_as_link(std::string{token}, link_color_);
1095  } else {
1096  result << token;
1097  }
1098  }
1099 
1100  return result.str();
1101 }
1102 
1103 bool pango_text::validate_markup(std::string_view text, char** raw_text, std::string& semi_escaped) const
1104 {
1105  if(pango_parse_markup(text.data(), text.size(),
1106  0, nullptr, raw_text, nullptr, nullptr)) {
1107  return true;
1108  }
1109 
1110  /*
1111  * The markup is invalid. Try to recover.
1112  *
1113  * The pango engine tested seems to accept stray single quotes »'« and
1114  * double quotes »"«. Stray ampersands »&« seem to give troubles.
1115  * So only try to recover from broken ampersands, by simply replacing them
1116  * with the escaped version.
1117  */
1118  semi_escaped = semi_escape_text(std::string(text));
1119 
1120  /*
1121  * If at least one ampersand is replaced the semi-escaped string
1122  * is longer than the original. If this isn't the case then the
1123  * markup wasn't (only) broken by ampersands in the first place.
1124  */
1125  if(text.size() == semi_escaped.size()
1126  || !pango_parse_markup(semi_escaped.c_str(), semi_escaped.size()
1127  , 0, nullptr, raw_text, nullptr, nullptr)) {
1128 
1129  /* Fixing the ampersands didn't work. */
1130  return false;
1131  }
1132 
1133  /* Replacement worked, still warn the user about the error. */
1134  WRN_GUI_L << "pango_text::" << __func__
1135  << " text '" << text
1136  << "' has unescaped ampersands '&', escaped them.";
1137 
1138  return true;
1139 }
1140 
1141 void pango_text::copy_layout_properties(PangoLayout& src, PangoLayout& dst)
1142 {
1143  pango_layout_set_alignment(&dst, pango_layout_get_alignment(&src));
1144  pango_layout_set_height(&dst, pango_layout_get_height(&src));
1145  pango_layout_set_ellipsize(&dst, pango_layout_get_ellipsize(&src));
1146 }
1147 
1148 std::vector<std::string> pango_text::get_lines() const
1149 {
1150  this->recalculate();
1151 
1152  PangoLayout* const layout = layout_.get();
1153  std::vector<std::string> res;
1154  int count = pango_layout_get_line_count(layout);
1155 
1156  if(count < 1) {
1157  return res;
1158  }
1159 
1160  using layout_iterator = std::unique_ptr<PangoLayoutIter, std::function<void(PangoLayoutIter*)>>;
1161  layout_iterator i{pango_layout_get_iter(layout), pango_layout_iter_free};
1162 
1163  res.reserve(count);
1164 
1165  do {
1166  PangoLayoutLine* ll = pango_layout_iter_get_line_readonly(i.get());
1167  const char* begin = &pango_layout_get_text(layout)[ll->start_index];
1168  res.emplace_back(begin, ll->length);
1169  } while(pango_layout_iter_next_line(i.get()));
1170 
1171  return res;
1172 }
1173 
1175 {
1176  static pango_text text_renderer;
1177  return text_renderer;
1178 }
1179 
1181 {
1182  // Reset metrics to defaults
1183  return get_text_renderer()
1184  .set_family_class(fclass)
1185  .set_font_style(style)
1188 }
1189 
1190 } // namespace font
1191 
1192 namespace std
1193 {
1195 {
1196  std::size_t hash = 0;
1197 
1198  boost::hash_combine(hash, t.text_);
1199  boost::hash_combine(hash, t.font_class_);
1200  boost::hash_combine(hash, t.font_size_);
1201  boost::hash_combine(hash, t.font_style_);
1202  boost::hash_combine(hash, t.foreground_color_.to_rgba_bytes());
1203  boost::hash_combine(hash, t.rect_.width);
1204  boost::hash_combine(hash, t.rect_.height);
1205  boost::hash_combine(hash, t.maximum_width_);
1206  boost::hash_combine(hash, t.maximum_height_);
1207  boost::hash_combine(hash, t.alignment_);
1208  boost::hash_combine(hash, t.ellipse_mode_);
1209  boost::hash_combine(hash, t.add_outline_);
1210 
1211  // Hash for the global attribute list
1212  boost::hash_combine(hash, t.attrib_hash_);
1213 
1214  return hash;
1215 }
1216 
1217 } // namespace std
double t
Definition: astarsearch.cpp:63
double g
Definition: astarsearch.cpp:63
Small helper class to make sure the pango font object is destroyed properly.
Definition: font.hpp:25
Text class.
Definition: text.hpp:79
int pixel_scale_
The pixel scale, used to render high-DPI text.
Definition: text.hpp:431
pango_text & set_font_style(const FONT_STYLE font_style)
Definition: text.cpp:597
PangoEllipsizeMode ellipse_mode_
The way too long text is shown depends on this mode.
Definition: text.hpp:397
void set_highlight_area(const unsigned start_offset, const unsigned end_offset, const color_t &color)
Mark a specific portion of text for highlighting.
Definition: text.cpp:497
void add_attribute_weight(const unsigned start_offset, const unsigned end_offset, PangoWeight weight)
Definition: text.cpp:375
static void copy_layout_properties(PangoLayout &src, PangoLayout &dst)
Definition: text.cpp:1141
bool add_outline_
Whether to add an outline effect.
Definition: text.hpp:358
bool validate_markup(std::string_view text, char **raw_text, std::string &semi_escaped) const
Definition: text.cpp:1103
bool set_markup(std::string_view text, PangoLayout &layout)
Sets the markup'ed text.
Definition: text.cpp:1035
int get_byte_offset(const unsigned column) const
Gets the correct number of columns to move the cursor from Pango.
Definition: text.cpp:199
pango_text & set_maximum_length(const std::size_t maximum_length)
Definition: text.cpp:698
void render(PangoLayout &layout, const SDL_Rect &viewport, const unsigned stride)
This is part of create_surface(viewport).
Definition: text.cpp:934
unsigned insert_text(const unsigned offset, const std::string &text)
Inserts UTF-8 text.
Definition: text.cpp:179
surface create_surface()
Equivalent to create_surface(viewport), where the viewport's top-left is at (0,0) and the area is lar...
Definition: text.cpp:985
PangoAlignment alignment_
The alignment of the text.
Definition: text.hpp:400
PangoRectangle rect_
Definition: text.hpp:325
int maximum_height_
The maximum height of the text.
Definition: text.hpp:394
point get_size()
Returns the size of the text, in drawing coordinates.
Definition: text.cpp:164
pango_text & set_characters_per_line(const unsigned characters_per_line)
Definition: text.cpp:632
color_t link_color_
The color to render links in.
Definition: text.hpp:343
void recalculate() const
Recalculates the text layout.
Definition: text.cpp:781
color_t foreground_color_
The foreground color.
Definition: text.hpp:355
point get_column_line(const point &position) const
Gets the column of line of the character at the position.
Definition: text.cpp:318
std::unique_ptr< PangoContext, std::function< void(void *)> > context_
Definition: text.hpp:323
bool link_aware_
Are hyperlinks in the text marked-up, and will get_link return them.
Definition: text.hpp:334
pango_text & set_foreground_color(const color_t &color)
Definition: text.cpp:607
std::size_t attrib_hash_
Hash for the global_attribute_list_.
Definition: text.hpp:428
int to_draw_scale(int s) const
Scale the given render-space size to draw-space, rounding up.
Definition: text.cpp:153
std::string format_links(std::string_view text) const
Replaces all instances of URLs in a given string with formatted links and returns the result.
Definition: text.cpp:1065
unsigned characters_per_line_
The number of characters per line.
Definition: text.hpp:386
bool markedup_text_
Does the text contain pango markup? If different render routines must be used.
Definition: text.hpp:331
void add_attribute_font_family(const unsigned start_offset, const unsigned end_offset, std::string family)
Definition: text.cpp:473
pango_text & set_family_class(font::family_class fclass)
Definition: text.cpp:575
font::family_class font_class_
The font family class used.
Definition: text.hpp:346
std::vector< std::string > get_lines() const
Retrieves a list of strings with contents for each rendered line.
Definition: text.cpp:1148
void add_attribute_fg_color(const unsigned start_offset, const unsigned end_offset, const color_t &color)
Definition: text.cpp:445
void update_pixel_scale()
Update pixel scale, if necessary.
Definition: text.cpp:760
unsigned font_size_
The font size to draw.
Definition: text.hpp:349
void add_attribute_size(const unsigned start_offset, const unsigned end_offset, int size)
Definition: text.cpp:352
texture render_texture(const SDL_Rect &viewport)
Wrapper around render_surface which sets texture::w() and texture::h() in the same way that render_an...
Definition: text.cpp:114
surface render_surface(const SDL_Rect &viewport)
Returns the rendered text.
Definition: text.cpp:139
std::vector< uint8_t > surface_buffer_
Buffer to store the image on.
Definition: text.hpp:479
pango_text & set_add_outline(bool do_add)
Definition: text.cpp:730
unsigned attribute_end_offset_
Definition: text.hpp:418
pango_text & set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
Definition: text.cpp:668
PangoRectangle calculate_size(PangoLayout &layout) const
Calculates surface size.
Definition: text.cpp:795
color_t highlight_color_
Definition: text.hpp:419
void add_attribute_underline(const unsigned start_offset, const unsigned end_offset, PangoUnderline underline)
Definition: text.cpp:421
pango_text & set_alignment(const PangoAlignment alignment)
Definition: text.cpp:688
std::string text_
The text to draw (stored as UTF-8).
Definition: text.hpp:328
point get_cursor_position(const unsigned column, const unsigned line=0) const
Gets the location for the cursor, in drawing coordinates.
Definition: text.cpp:222
bool calculation_dirty_
The text has two dirty states:
Definition: text.hpp:412
std::unique_ptr< PangoLayout, std::function< void(void *)> > layout_
Definition: text.hpp:324
pango_text & set_font_size(unsigned font_size)
Definition: text.cpp:585
pango_text & set_link_aware(bool b)
Definition: text.cpp:711
FONT_STYLE font_style_
The style of the font, this is an orred mask of the font flags.
Definition: text.hpp:352
unsigned attribute_start_offset_
Definition: text.hpp:417
std::string get_token(const 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:271
bool set_text(const std::string &text, const bool markedup)
Sets the text to render.
Definition: text.cpp:530
bool is_truncated() const
Has the text been truncated? This happens if it exceeds max width or height.
Definition: text.cpp:172
void add_attribute_style(const unsigned start_offset, const unsigned end_offset, PangoStyle style)
Definition: text.cpp:398
texture with_draw_scale(const texture &t) const
Adjust a texture's draw-width and height according to pixel scale.
Definition: text.cpp:146
std::size_t maximum_length_
The maximum length of the text.
Definition: text.hpp:403
pango_text & set_maximum_height(int height, bool multiline)
Definition: text.cpp:643
pango_text & set_maximum_width(int width)
Definition: text.cpp:616
std::size_t get_maximum_length() const
Get maximum length.
Definition: text.cpp:266
PangoAttrList * global_attribute_list_
Global pango attribute list.
Definition: text.hpp:425
texture render_and_get_texture()
Returns the cached texture, or creates a new one otherwise.
Definition: text.cpp:119
pango_text & set_link_color(const color_t &color)
Definition: text.cpp:720
std::string get_link(const 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:303
void clear_attribute_list()
Clear all attributes.
Definition: text.cpp:525
const std::string & text() const
Definition: text.hpp:270
std::size_t length_
Length of the text.
Definition: text.hpp:415
int get_max_glyph_height() const
Returns the maximum glyph height of a font, in drawing coordinates.
Definition: text.cpp:740
int maximum_width_
The maximum width of the text.
Definition: text.hpp:368
static prefs & get()
int font_scaled(int size)
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition: texture.hpp:33
void set_draw_size(int w, int h)
Set the intended size of the texture, in draw-space.
Definition: texture.hpp:131
std::size_t i
Definition: function.cpp:968
int w
static std::string _(const char *str)
Definition: gettext.hpp:93
Define the common log macros for the gui toolkit.
#define DBG_GUI_L
Definition: log.hpp:55
#define ERR_GUI_L
Definition: log.hpp:58
#define WRN_GUI_L
Definition: log.hpp:57
#define DBG_GUI_D
Definition: log.hpp:29
CURSOR_TYPE get()
Definition: cursor.cpp:216
static void layout()
void point(int x, int y)
Draw a single point.
Definition: draw.cpp:202
void rect(const SDL_Rect &rect)
Draw a rectangle.
Definition: draw.cpp:150
void line(int from_x, int from_y, int to_x, int to_y)
Draw a line.
Definition: draw.cpp:180
Collection of helper functions relating to Pango formatting.
family_class
Font classes for get_font_families().
@ FONT_SANS_SERIF
int get_max_height(unsigned size, font::family_class fclass, pango_text::FONT_STYLE style)
Returns the maximum glyph height of a font, in pixels.
Definition: text.cpp:1180
pango_text & get_text_renderer()
Returns a reference to a static pango_text object.
Definition: text.cpp:1174
constexpr float get_line_spacing_factor()
Definition: text.hpp:545
bool looks_like_url(std::string_view str)
Definition: hyperlink.hpp:27
std::string format_as_link(const std::string &link, color_t color)
Definition: hyperlink.hpp:32
std::string semi_escape_text(const std::string &text)
Definition: escape.hpp:52
const t_string & get_font_families(family_class fclass)
Returns the currently defined fonts.
static void unpremultiply(uint8_t &value, const unsigned div)
Definition: text.cpp:903
static void from_cairo_format(uint32_t &c)
Converts from cairo-format ARGB32 premultiplied alpha to plain alpha.
Definition: text.cpp:919
void flush_texture_cache()
Flush the rendered text cache.
Definition: text.cpp:61
static constexpr inverse_table inverse_table_
Definition: text.cpp:897
std::string_view debug_truncate(std::string_view text)
Returns a truncated version of the text.
Definition: helper.cpp:153
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
std::string & insert(std::string &str, const std::size_t pos, const std::string &insert)
Insert a UTF-8 string at the specified position.
Definition: unicode.cpp:98
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
std::string & truncate(std::string &str, const std::size_t size)
Truncates a UTF-8 string to the specified number of characters.
Definition: unicode.cpp:116
int get_pixel_scale()
Get the current active pixel scale multiplier.
Definition: video.cpp:483
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:59
constexpr uint32_t to_rgba_bytes() const
Returns the stored color as a uint32_t, in RGBA format.
Definition: color.hpp:149
unsigned values[256]
Definition: text.cpp:884
constexpr inverse_table()
Definition: text.cpp:886
unsigned operator[](uint8_t i) const
Definition: text.cpp:894
Holds a 2D point.
Definition: point.hpp:25
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:47
std::size_t operator()(const font::pango_text &) const
Definition: text.cpp:1194
mock_char c
mock_party p
#define DBG_FT
Definition: text.cpp:41
static lg::log_domain log_font("font")
#define d
#define f
#define a
#define b