lxgui
gui_text.cpp
1 #include "lxgui/gui_text.hpp"
2 
3 #include "lxgui/gui_exception.hpp"
4 #include "lxgui/gui_font.hpp"
5 #include "lxgui/gui_material.hpp"
6 #include "lxgui/gui_matrix4.hpp"
7 #include "lxgui/gui_out.hpp"
8 #include "lxgui/gui_quad.hpp"
9 #include "lxgui/gui_renderer.hpp"
10 #include "lxgui/gui_vertex_cache.hpp"
11 #include "lxgui/utils.hpp"
12 #include "lxgui/utils_range.hpp"
13 
14 #include <map>
15 
16 // #define DEBUG_LOG(msg) gui::out << (msg) << std::endl
17 #define DEBUG_LOG(msg)
18 
19 namespace lxgui::gui {
20 
23 namespace parser {
24 enum class color_action { none, set, reset };
25 
26 struct format {
27  color col = color::white;
28  color_action action = color_action::none;
29 };
30 
31 struct texture {
32  std::string file_name;
33  float width = 0.0f;
34  float height = 0.0f;
35  std::shared_ptr<material> mat;
36 };
37 
38 using item = std::variant<char32_t, format, texture>;
39 
40 struct line {
41  std::vector<item> content;
42  float width = 0.0f;
43 };
44 
45 std::vector<item>
46 parse_string(renderer& renderer, const utils::ustring_view& caption, bool formatting_enabled) {
47  std::vector<item> content;
48  for (auto iter_char = caption.begin(); iter_char != caption.end(); ++iter_char) {
49  // Read format tags
50  if (*iter_char == U'|' && formatting_enabled) {
51  ++iter_char;
52  if (iter_char == caption.end())
53  break;
54 
55  if (*iter_char != U'|') {
56  if (*iter_char == U'r') {
57  format format;
58  format.action = color_action::reset;
59  content.push_back(format);
60  } else if (*iter_char == U'c') {
61  format format;
62  format.action = color_action::set;
63 
64  auto read_two = [&](float& out_value) {
65  ++iter_char;
66  if (iter_char == caption.end())
67  return false;
68  utils::ustring color_part(2, U'0');
69  color_part[0] = *iter_char;
70  ++iter_char;
71  if (iter_char == caption.end())
72  return false;
73  color_part[1] = *iter_char;
74  out_value = utils::hex_to_uint(utils::unicode_to_utf8(color_part)) / 255.0f;
75  return true;
76  };
77 
78  if (!read_two(format.col.a))
79  break;
80  if (!read_two(format.col.r))
81  break;
82  if (!read_two(format.col.g))
83  break;
84  if (!read_two(format.col.b))
85  break;
86 
87  content.push_back(format);
88  } else if (*iter_char == U'T') {
89  ++iter_char;
90 
91  const auto begin = iter_char - caption.begin();
92  const auto pos = caption.find(U"|t", begin);
93  if (pos == caption.npos)
94  break;
95 
96  const std::string extracted =
97  utils::unicode_to_utf8(caption.substr(begin, pos - begin));
98 
99  const auto words = utils::cut(extracted, ":");
100  if (!words.empty()) {
101  texture texture;
102  texture.mat = renderer.create_atlas_material("GUI", std::string{words[0]});
103  texture.width = texture.height = std::numeric_limits<float>::quiet_NaN();
104 
105  if (words.size() == 2) {
106  texture.width =
107  utils::from_string<float>(words[1]).value_or(texture.width);
108  texture.height = texture.width;
109  } else if (words.size() > 2) {
110  texture.width =
111  utils::from_string<float>(words[1]).value_or(texture.width);
112  texture.height =
113  utils::from_string<float>(words[2]).value_or(texture.height);
114  }
115 
116  content.push_back(texture);
117  }
118 
119  iter_char += extracted.size() + 1;
120  }
121 
122  continue;
123  }
124  }
125 
126  // Add characters
127  content.push_back(*iter_char);
128  }
129 
130  return content;
131 }
132 
133 bool is_whitespace(const item& i) {
134  return std::visit(
135  [](const auto& value) {
136  using type = std::decay_t<decltype(value)>;
137  if constexpr (std::is_same_v<type, char32_t>) {
138  return utils::is_whitespace(value);
139  } else {
140  return false;
141  }
142  },
143  i);
144 }
145 
146 bool is_word(const item& i) {
147  return std::visit(
148  [](const auto& value) {
149  using type = std::decay_t<decltype(value)>;
150  if constexpr (std::is_same_v<type, char32_t>) {
151  return !utils::is_whitespace(value);
152  } else {
153  return false;
154  }
155  },
156  i);
157 }
158 
159 bool is_character(const item& i) {
160  return i.index() == 0u;
161 }
162 
163 bool is_format(const item& i) {
164  return i.index() == 1u;
165 }
166 
167 bool is_character(const item& i, char32_t c) {
168  return i.index() == 0u && std::get<char32_t>(i) == c;
169 }
170 
171 float get_width(const text& text, const item& i) {
172  return std::visit(
173  [&](const auto& value) {
174  using type = std::decay_t<decltype(value)>;
175  if constexpr (std::is_same_v<type, char32_t>) {
176  return text.get_character_width(value);
177  } else if constexpr (std::is_same_v<type, texture>) {
178  if (std::isnan(value.width))
179  return text.get_line_height();
180  else
181  return value.width * text.get_scaling_factor();
182  } else {
183  return 0.0f;
184  }
185  },
186  i);
187 }
188 
189 float get_kerning(const text& txt, const item& i1, const item& i2) {
190  return std::visit(
191  [&](const auto& value1) {
192  using type1 = std::decay_t<decltype(value1)>;
193  if constexpr (std::is_same_v<type1, char32_t>) {
194  return std::visit(
195  [&](const auto& value2) {
196  using type2 = std::decay_t<decltype(value2)>;
197  if constexpr (std::is_same_v<type2, char32_t>) {
198  return txt.get_character_kerning(value1, value2);
199  } else {
200  return 0.0f;
201  }
202  },
203  i2);
204  } else {
205  return 0.0f;
206  }
207  },
208  i1);
209 }
210 
211 float get_tracking(const text& txt, const item& i) {
212  return std::visit(
213  [&](const auto& value) {
214  using type = std::decay_t<decltype(value)>;
215  if constexpr (std::is_same_v<type, char32_t>) {
216  if (value != U'\n')
217  return txt.get_tracking();
218  else
219  return 0.0f;
220  } else {
221  return 0.0f;
222  }
223  },
224  i);
225 }
226 
227 std::pair<float, float> get_advance(
228  const text& txt,
229  std::vector<item>::const_iterator iter_char,
230  std::vector<item>::const_iterator iter_begin) {
231  float advance = parser::get_width(txt, *iter_char);
232  float kerning = 0.0f;
233 
234  auto iter_prev = iter_char;
235  while (iter_prev != iter_begin) {
236  --iter_prev;
237  if (parser::is_format(*iter_prev))
238  continue;
239 
240  kerning = parser::get_tracking(txt, *iter_char);
241 
242  if (!parser::is_whitespace(*iter_char) && !parser::is_whitespace(*iter_prev))
243  kerning += parser::get_kerning(txt, *iter_prev, *iter_char);
244 
245  break;
246  }
247 
248  return std::make_pair(kerning, advance);
249 }
250 
251 float get_full_advance(
252  const text& txt,
253  std::vector<item>::const_iterator iter_char,
254  std::vector<item>::const_iterator iter_begin) {
255  const auto advance = get_advance(txt, iter_char, iter_begin);
256  return advance.first + advance.second;
257 }
258 
259 float get_string_width(const text& txt, const std::vector<item>& content) {
260  float width = 0.0f;
261  float max_width = 0.0f;
262 
263  for (auto iter_char : utils::range::iterator(content)) {
264  if (parser::is_character(*iter_char, U'\n')) {
265  if (width > max_width)
266  max_width = width;
267 
268  width = 0.0f;
269  } else {
270  width += parser::get_full_advance(txt, iter_char, content.begin());
271  }
272  }
273 
274  if (width > max_width)
275  max_width = width;
276 
277  return max_width;
278 }
279 } // namespace parser
284  renderer& rdr, std::shared_ptr<const font> fnt, std::shared_ptr<const font> outline_font) :
285  renderer_(rdr), font_(std::move(fnt)), outline_font_(std::move(outline_font)) {}
286 
287 float text::get_line_height() const {
288  if (font_)
289  return font_->get_size() * scaling_factor_;
290  else
291  return 0.0;
292 }
293 
294 void text::set_scaling_factor(float scaling_factor) {
295  if (scaling_factor_ == scaling_factor)
296  return;
297 
298  scaling_factor_ = scaling_factor;
299  notify_cache_dirty_();
300 }
301 
303  return scaling_factor_;
304 }
305 
306 void text::set_text(const utils::ustring& content) {
307  if (unicode_text_ == content)
308  return;
309 
310  unicode_text_ = content;
311 
312  notify_cache_dirty_();
313 }
314 
315 const utils::ustring& text::get_text() const {
316  return unicode_text_;
317 }
318 
319 void text::set_color(const color& c, bool force_color) {
320  if (color_ == c && force_color_ == force_color)
321  return;
322 
323  color_ = c;
324  force_color_ = force_color;
325 
326  notify_vertex_cache_dirty_();
327 }
328 
329 const color& text::get_color() const {
330  return color_;
331 }
332 
333 void text::set_alpha(float alpha) {
334  if (alpha == alpha_)
335  return;
336 
337  alpha_ = alpha;
338 
339  notify_vertex_cache_dirty_();
340 }
341 
342 float text::get_alpha() const {
343  return alpha_;
344 }
345 
346 void text::set_box_dimensions(float box_width, float box_height) {
347  if (box_width_ == box_width && box_height_ == box_height)
348  return;
349 
350  box_width_ = box_width;
351  box_height_ = box_height;
352 
353  notify_cache_dirty_();
354 }
355 
356 void text::set_box_width(float box_width) {
357  if (box_width_ == box_width)
358  return;
359 
360  box_width_ = box_width;
361 
362  notify_cache_dirty_();
363 }
364 
365 void text::set_box_height(float box_height) {
366  if (box_height_ == box_height)
367  return;
368 
369  box_height_ = box_height;
370 
371  notify_cache_dirty_();
372 }
373 
374 float text::get_width() const {
375  update_();
376  return width_;
377 }
378 
379 float text::get_height() const {
380  update_();
381  return height_;
382 }
383 
384 float text::get_box_width() const {
385  return box_width_;
386 }
387 
388 float text::get_box_height() const {
389  return box_height_;
390 }
391 
392 float text::get_text_width() const {
393  return get_string_width(unicode_text_);
394 }
395 
396 float text::get_text_height() const {
397  if (!font_)
398  return 0.0f;
399 
400  std::size_t count = std::count(unicode_text_.begin(), unicode_text_.end(), U'\n');
401  float height = (1.0f + count * line_spacing_) * get_line_height();
402 
403  return height;
404 }
405 
406 std::size_t text::get_line_count() const {
407  update_();
408  return num_lines_;
409 }
410 
411 float text::get_string_width(const std::string& content) const {
412  return get_string_width(utils::utf8_to_unicode(content));
413 }
414 
415 float text::get_string_width(const utils::ustring& content) const {
416  if (!font_)
417  return 0.0f;
418 
419  return parser::get_string_width(
420  *this, parser::parse_string(renderer_, content, formatting_enabled_));
421 }
422 
423 float text::get_character_width(char32_t c) const {
424  if (!font_)
425  return 0.0f;
426  else if (c == U'\t')
427  return 4.0f * font_->get_character_width(U' ') * scaling_factor_;
428  else
429  return font_->get_character_width(c) * scaling_factor_;
430 }
431 
432 float text::get_character_kerning(char32_t c1, char32_t c2) const {
433  return font_->get_character_kerning(c1, c2) * scaling_factor_;
434 }
435 
437  if (align_x_ == align_x)
438  return;
439 
440  align_x_ = align_x;
441 
442  notify_cache_dirty_();
443 }
444 
446  if (align_y_ == align_y)
447  return;
448 
449  align_y_ = align_y;
450 
451  notify_cache_dirty_();
452 }
453 
455  return align_x_;
456 }
457 
459  return align_y_;
460 }
461 
462 void text::set_tracking(float tracking) {
463  if (tracking_ == tracking)
464  return;
465 
466  tracking_ = tracking;
467 
468  notify_cache_dirty_();
469 }
470 
471 float text::get_tracking() const {
472  return tracking_;
473 }
474 
475 void text::set_line_spacing(float line_spacing) {
476  if (line_spacing_ == line_spacing)
477  return;
478 
479  line_spacing_ = line_spacing;
480 
481  notify_cache_dirty_();
482 }
483 
484 float text::get_line_spacing() const {
485  return line_spacing_;
486 }
487 
488 void text::set_remove_starting_spaces(bool remove_starting_spaces) {
489  if (remove_starting_spaces_ == remove_starting_spaces)
490  return;
491 
492  remove_starting_spaces_ = remove_starting_spaces;
493 
494  notify_cache_dirty_();
495 }
496 
498  return remove_starting_spaces_;
499 }
500 
502  if (word_wrap_enabled_ == wrap)
503  return;
504 
505  word_wrap_enabled_ = wrap;
506 
507  notify_cache_dirty_();
508 }
509 
511  return word_wrap_enabled_;
512 }
513 
514 void text::set_word_ellipsis_enabled(bool add_ellipsis) {
515  if (ellipsis_enabled_ == add_ellipsis)
516  return;
517 
518  ellipsis_enabled_ = add_ellipsis;
519 
520  notify_cache_dirty_();
521 }
522 
524  return ellipsis_enabled_;
525 }
526 
527 void text::set_formatting_enabled(bool formatting) {
528  if (formatting == formatting_enabled_)
529  return;
530 
531  formatting_enabled_ = formatting;
532 
533  notify_vertex_cache_dirty_();
534 }
535 
536 void text::set_use_vertex_cache(bool use_vertex_cache) {
537  use_vertex_cache_flag_ = use_vertex_cache;
538 }
539 
541  return use_vertex_cache_flag_;
542 }
543 
544 bool text::use_vertex_cache_() const {
545  return renderer_.is_vertex_cache_supported() && use_vertex_cache_flag_;
546 }
547 
548 void text::render(const matrix4f& transform) const {
549  if (!font_ || unicode_text_.empty())
550  return;
551 
552  update_();
553 
554  bool use_vertex_cache = use_vertex_cache_();
555  if (use_vertex_cache) {
556  update_vertex_cache_();
557  }
558 
559  if (outline_font_) {
560  if (const auto mat = outline_font_->get_texture().lock()) {
561  if (use_vertex_cache && outline_vertex_cache_) {
562  renderer_.render_cache(mat.get(), *outline_vertex_cache_, transform);
563  } else {
564  std::vector<std::array<vertex, 4>> quads_copy = outline_quad_list_;
565  for (auto& quad : quads_copy) {
566  for (std::size_t i = 0; i < 4; ++i) {
567  quad[i].pos = quad[i].pos * transform;
568  quad[i].col.a *= alpha_;
569  }
570  }
571 
572  renderer_.render_quads(mat.get(), quads_copy);
573  }
574  }
575  }
576 
577  if (const auto mat = font_->get_texture().lock()) {
578  if (use_vertex_cache && vertex_cache_) {
579  renderer_.render_cache(mat.get(), *vertex_cache_, transform);
580  } else {
581  std::vector<std::array<vertex, 4>> quads_copy = quad_list_;
582  for (auto& quad : quads_copy) {
583  for (std::size_t i = 0; i < 4; ++i) {
584  quad[i].pos = quad[i].pos * transform;
585 
586  if (!formatting_enabled_ || force_color_ || quad[i].col == color::empty) {
587  quad[i].col = color_;
588  }
589 
590  quad[i].col.a *= alpha_;
591  }
592  }
593 
594  renderer_.render_quads(mat.get(), quads_copy);
595  }
596 
597  for (auto quad : icons_list_) {
598  for (std::size_t i = 0; i < 4; ++i) {
599  quad.v[i].pos = quad.v[i].pos * transform;
600  quad.v[i].col.a *= alpha_;
601  }
602 
603  renderer_.render_quad(quad);
604  }
605  }
606 }
607 
608 void text::notify_cache_dirty_() const {
609  update_cache_flag_ = true;
610 }
611 
612 void text::notify_vertex_cache_dirty_() const {
613  update_vertex_cache_flag_ = true;
614 }
615 
616 float text::round_to_pixel_(float value, utils::rounding_method method) const {
617  return utils::round(value, scaling_factor_, method);
618 }
619 
620 void text::update_() const {
621  if (!font_ || !update_cache_flag_)
622  return;
623 
624  // Update the line list, read format tags, do word wrapping, ...
625  std::vector<parser::line> line_list;
626 
627  DEBUG_LOG(" Get max line nbr");
628  std::size_t max_line_nbr = 0;
629  if (box_height_ != 0.0f && !std::isinf(box_height_)) {
630  if (box_height_ < get_line_height()) {
631  max_line_nbr = 0;
632  } else {
633  float remaining = box_height_ - get_line_height();
634  max_line_nbr = 1 + static_cast<std::size_t>(
635  std::floor(remaining / (get_line_height() * line_spacing_)));
636  }
637  } else
638  max_line_nbr = std::numeric_limits<std::size_t>::max();
639 
640  if (max_line_nbr != 0) {
641  auto manual_line_list = utils::cut_each(unicode_text_, U"\n");
642  for (auto iter_manual : utils::range::iterator(manual_line_list)) {
643  DEBUG_LOG(" Line: '" + utils::unicode_to_utf8(*iterManual) + "'");
644 
645  // Parse the line
646  std::vector<parser::item> parsed_content =
647  parser::parse_string(renderer_, *iter_manual, formatting_enabled_);
648 
649  // Make a temporary line array
650  std::vector<parser::line> lines;
651 
652  auto iter_line_begin = parsed_content.begin();
653  parser::line line;
654  line.width = 0.0f;
655 
656  bool done = false;
657  for (auto iter_char1 = parsed_content.begin(); iter_char1 != parsed_content.end();
658  ++iter_char1) {
659  DEBUG_LOG(" Get width");
660  line.width += parser::get_full_advance(*this, iter_char1, iter_line_begin);
661  line.content.push_back(*iter_char1);
662 
663  if (round_to_pixel_(line.width - box_width_) > 0) {
664  DEBUG_LOG(
665  " Box break " + utils::to_string(line.width) + " > " +
666  utils::to_string(box_width_));
667 
668  // Whoops, the line is too long...
669  auto iter_space = std::find_if(
670  line.content.begin(), line.content.end(), &parser::is_whitespace);
671 
672  if (iter_space != line.content.end() && word_wrap_enabled_) {
673  DEBUG_LOG(" Spaced");
674  // There are several words on this line, we'll
675  // be able to put the last one on the next line
676  auto iter_char2 = iter_char1 + 1;
677  std::vector<parser::item> erased_content;
678  std::size_t chars_to_erase = 0;
679  float last_word_width = 0.0f;
680  bool last_was_word = false;
681  while (line.width > box_width_ && iter_char2 != iter_line_begin) {
682  --iter_char2;
683 
684  if (parser::is_whitespace(*iter_char2)) {
685  if (!last_was_word || remove_starting_spaces_ ||
686  line.width - last_word_width > box_width_) {
687  last_word_width += parser::get_full_advance(
688  *this, iter_char2, iter_line_begin);
689  erased_content.insert(erased_content.begin(), *iter_char2);
690  ++chars_to_erase;
691 
692  line.width -= last_word_width;
693  last_word_width = 0.0f;
694  } else
695  break;
696  } else {
697  last_word_width +=
698  parser::get_full_advance(*this, iter_char2, iter_line_begin);
699  erased_content.insert(erased_content.begin(), *iter_char2);
700  ++chars_to_erase;
701 
702  last_was_word = true;
703  }
704  }
705 
706  if (remove_starting_spaces_) {
707  while (iter_char2 != iter_char1 + 1 &&
708  parser::is_whitespace(*iter_char2)) {
709  --chars_to_erase;
710  erased_content.erase(erased_content.begin());
711  ++iter_char2;
712  }
713  }
714 
715  line.width -= last_word_width;
716  line.content.erase(line.content.end() - chars_to_erase, line.content.end());
717  lines.push_back(line);
718 
719  line.width = parser::get_string_width(*this, erased_content);
720  line.content = erased_content;
721  iter_line_begin = iter_char1 - (line.content.size() - 1u);
722  } else {
723  DEBUG_LOG(" Single word");
724  // There is only one word on this line, or word
725  // wrap is disabled. Anyway, this line is just
726  // too long for the text box: our only option
727  // is to truncate it.
728  if (ellipsis_enabled_) {
729  DEBUG_LOG(" Ellipsis");
730  // FIXME: this doesn't account for kerning between the "..." and prev
731  // char
732  float word_width = get_string_width(U"...");
733  auto iter_char2 = iter_char1 + 1;
734  std::size_t chars_to_erase = 0;
735  while (line.width + word_width > box_width_ &&
736  iter_char2 != iter_line_begin) {
737  --iter_char2;
738  line.width -=
739  parser::get_full_advance(*this, iter_char2, iter_line_begin);
740  ++chars_to_erase;
741  }
742 
743  DEBUG_LOG(
744  " Char to erase: " + utils::to_string(chars_to_erase) +
745  " / " + utils::to_string(line.content.size()));
746 
747  line.content.erase(
748  line.content.end() - chars_to_erase, line.content.end());
749  line.content.push_back(U'.');
750  line.content.push_back(U'.');
751  line.content.push_back(U'.');
752  line.width += word_width;
753  } else {
754  DEBUG_LOG(" Truncate");
755  auto iter_char2 = iter_char1 + 1;
756  std::size_t chars_to_erase = 0;
757  while (line.width > box_width_ && iter_char2 != iter_line_begin) {
758  --iter_char2;
759  line.width -=
760  parser::get_full_advance(*this, iter_char2, iter_line_begin);
761  ++chars_to_erase;
762  }
763 
764  line.content.erase(
765  line.content.end() - chars_to_erase, line.content.end());
766  }
767 
768  if (!word_wrap_enabled_) {
769  DEBUG_LOG(" Display single line");
770  // Word wrap is disabled, so we can only display one line anyway.
771  line_list.push_back(line);
772  done = true;
773  break;
774  }
775 
776  // Add the line
777  lines.push_back(line);
778  line.width = 0.0f;
779  line.content.clear();
780 
781  DEBUG_LOG(" Continue");
782 
783  // Skip all following content (which we cannot display) until next
784  // whitespace
785  auto iter_temp = iter_char1;
786  iter_char1 =
787  std::find_if(iter_char1, parsed_content.end(), &parser::is_whitespace);
788 
789  if (iter_char1 == parsed_content.end())
790  break;
791 
792  // Apply the format tags that were cut
793  for (; iter_temp != iter_char1; ++iter_temp) {
794  std::visit(
795  [&](const auto& value) {
796  using type = std::decay_t<decltype(value)>;
797  if constexpr (std::is_same_v<type, parser::format>) {
798  line.content.push_back(value);
799  }
800  },
801  *iter_temp);
802  }
803 
804  // Look for the next word
805  iter_char1 =
806  std::find_if(iter_char1, parsed_content.end(), &parser::is_word);
807  if (iter_char1 != parsed_content.end())
808  break;
809 
810  --iter_char1;
811  iter_line_begin = iter_char1;
812  }
813  }
814  }
815 
816  if (done)
817  break;
818 
819  DEBUG_LOG(" End");
820 
821  lines.push_back(line);
822 
823  // Add the maximum number of line to this text
824  for (auto& l : lines) {
825  if (line_list.size() == max_line_nbr) {
826  done = true;
827  break;
828  }
829  line_list.push_back(std::move(l));
830  }
831 
832  if (done)
833  break;
834  DEBUG_LOG(" .");
835  }
836  }
837 
838  num_lines_ = line_list.size();
839 
840  quad_list_.clear();
841  outline_quad_list_.clear();
842  icons_list_.clear();
843 
844  if (!line_list.empty()) {
845  if (box_width_ == 0.0f || std::isinf(box_width_)) {
846  width_ = 0.0f;
847  for (const auto& line : line_list)
848  width_ = std::max(width_, line.width);
849  } else
850  width_ = box_width_;
851 
852  height_ =
853  (1.0f + static_cast<float>(line_list.size() - 1) * line_spacing_) * get_line_height();
854 
855  float y = 0.0f;
856  float x0 = 0.0f;
857 
858  if (box_width_ != 0.0f && !std::isinf(box_width_)) {
859  switch (align_x_) {
860  case alignment_x::left: x0 = 0.0f; break;
861  case alignment_x::center: x0 = box_width_ * 0.5f; break;
862  case alignment_x::right: x0 = box_width_; break;
863  }
864  } else
865  x0 = 0.0f;
866 
867  if (!std::isinf(box_height_)) {
868  switch (align_y_) {
869  case alignment_y::top: y = 0.0f; break;
870  case alignment_y::middle: y = (box_height_ - height_) * 0.5f; break;
871  case alignment_y::bottom: y = (box_height_ - height_); break;
872  }
873  } else {
874  switch (align_y_) {
875  case alignment_y::top: y = 0.0f; break;
876  case alignment_y::middle: y = -height_ * 0.5f; break;
877  case alignment_y::bottom: y = -height_; break;
878  }
879  }
880 
881  x0 = round_to_pixel_(x0);
882  y = round_to_pixel_(y);
883 
884  std::vector<color> color_stack;
885 
886  for (const auto& line : line_list) {
887  float x = 0.0f;
888  switch (align_x_) {
889  case alignment_x::left: x = 0.0f; break;
890  case alignment_x::center: x = -line.width * 0.5f; break;
891  case alignment_x::right: x = -line.width; break;
892  }
893 
894  x = round_to_pixel_(x) + x0;
895 
896  for (auto iter_char : utils::range::iterator(line.content)) {
897  const auto advance = parser::get_advance(*this, iter_char, line.content.begin());
898 
899  x += advance.first;
900 
901  std::visit(
902  [&](const auto& value) {
903  using type = std::decay_t<decltype(value)>;
904  if constexpr (std::is_same_v<type, parser::format>) {
905  switch (value.action) {
906  case parser::color_action::set: color_stack.push_back(value.col); break;
907  case parser::color_action::reset: color_stack.pop_back(); break;
908  default: break;
909  }
910  } else if constexpr (std::is_same_v<type, parser::texture>) {
911  float tex_width = 0.0f, tex_height = 0.0f;
912  if (std::isnan(value.width)) {
913  tex_width = get_line_height();
914  tex_height = get_line_height();
915  } else {
916  tex_width = value.width * get_scaling_factor();
917  tex_height = value.height * get_scaling_factor();
918  }
919 
920  tex_width = round_to_pixel_(tex_width);
921  tex_height = round_to_pixel_(tex_height);
922 
923  quad icon;
924  icon.mat = value.mat;
925  icon.v[0].pos = vector2f(0.0f, 0.0f);
926  icon.v[1].pos = vector2f(tex_width, 0.0f);
927  icon.v[2].pos = vector2f(tex_width, tex_height);
928  icon.v[3].pos = vector2f(0.0f, tex_height);
929  if (icon.mat) {
930  icon.v[0].uvs = icon.mat->get_canvas_uv(vector2f(0.0f, 0.0f), true);
931  icon.v[1].uvs = icon.mat->get_canvas_uv(vector2f(1.0f, 0.0f), true);
932  icon.v[2].uvs = icon.mat->get_canvas_uv(vector2f(1.0f, 1.0f), true);
933  icon.v[3].uvs = icon.mat->get_canvas_uv(vector2f(0.0f, 1.0f), true);
934  }
935 
936  for (std::size_t i = 0; i < 4; ++i) {
937  icon.v[i].pos += vector2f(round_to_pixel_(x), round_to_pixel_(y));
938  }
939 
940  icons_list_.push_back(icon);
941  } else if constexpr (std::is_same_v<type, char32_t>) {
942  if (outline_font_) {
943  std::array<vertex, 4> vertex_list =
944  create_outline_letter_quad_(value);
945  for (std::size_t i = 0; i < 4; ++i) {
946  vertex_list[i].pos +=
947  vector2f(round_to_pixel_(x), round_to_pixel_(y));
948  vertex_list[i].col = color::black;
949  }
950 
951  outline_quad_list_.push_back(vertex_list);
952  }
953 
954  std::array<vertex, 4> vertex_list = create_letter_quad_(value);
955  for (std::size_t i = 0; i < 4; ++i) {
956  vertex_list[i].pos +=
957  vector2f(round_to_pixel_(x), round_to_pixel_(y));
958  vertex_list[i].col =
959  color_stack.empty() ? color::empty : color_stack.back();
960  }
961 
962  quad_list_.push_back(vertex_list);
963  }
964  },
965  *iter_char);
966 
967  x += advance.second;
968  }
969 
970  y += get_line_height() * line_spacing_;
971  }
972  } else {
973  width_ = 0.0f;
974  height_ = 0.0f;
975  }
976 
977  update_cache_flag_ = false;
978 
979  notify_vertex_cache_dirty_();
980 }
981 
982 void text::update_vertex_cache_() const {
983  if (!update_vertex_cache_flag_)
984  return;
985 
986  if (!vertex_cache_)
987  vertex_cache_ = renderer_.create_vertex_cache(vertex_cache::type::quads);
988 
989  std::vector<std::array<vertex, 4>> quads_copy = quad_list_;
990  for (auto& quad : quads_copy) {
991  for (std::size_t i = 0; i < 4; ++i) {
992  if (!formatting_enabled_ || force_color_ || quad[i].col == color::empty) {
993  quad[i].col = color_;
994  }
995 
996  quad[i].col.a *= alpha_;
997  }
998  }
999 
1000  vertex_cache_->update(quads_copy[0].data(), quads_copy.size() * 4);
1001 
1002  if (outline_font_) {
1003  if (!outline_vertex_cache_)
1004  outline_vertex_cache_ = renderer_.create_vertex_cache(vertex_cache::type::quads);
1005 
1006  std::vector<std::array<vertex, 4>> outline_quads_copy = outline_quad_list_;
1007  for (auto& quad : outline_quads_copy) {
1008  for (std::size_t i = 0; i < 4; ++i) {
1009  quad[i].col.a *= alpha_;
1010  }
1011  }
1012 
1013  outline_vertex_cache_->update(outline_quads_copy[0].data(), outline_quads_copy.size() * 4);
1014  }
1015 
1016  update_vertex_cache_flag_ = false;
1017 }
1018 
1019 std::array<vertex, 4> text::create_letter_quad_(const gui::font& font, char32_t c) const {
1020  bounds2f quad = font.get_character_bounds(c) * scaling_factor_;
1021 
1022  std::array<vertex, 4> vertex_list;
1023  vertex_list[0].pos = quad.top_left();
1024  vertex_list[1].pos = quad.top_right();
1025  vertex_list[2].pos = quad.bottom_right();
1026  vertex_list[3].pos = quad.bottom_left();
1027 
1028  bounds2f uvs = font.get_character_uvs(c);
1029  vertex_list[0].uvs = uvs.top_left();
1030  vertex_list[1].uvs = uvs.top_right();
1031  vertex_list[2].uvs = uvs.bottom_right();
1032  vertex_list[3].uvs = uvs.bottom_left();
1033 
1034  return vertex_list;
1035 }
1036 
1037 std::array<vertex, 4> text::create_letter_quad_(char32_t c) const {
1038  return create_letter_quad_(*font_, c);
1039 }
1040 
1041 std::array<vertex, 4> text::create_outline_letter_quad_(char32_t c) const {
1042  return create_letter_quad_(*outline_font_, c);
1043 }
1044 
1045 quad text::create_letter_quad(char32_t c) const {
1046  quad output;
1047  output.mat = font_->get_texture().lock();
1048  output.v = create_letter_quad_(c);
1049 
1050  return output;
1051 }
1052 
1053 std::size_t text::get_letter_count() const {
1054  update_();
1055  return quad_list_.size();
1056 }
1057 
1058 const std::array<vertex, 4>& text::get_letter_quad(std::size_t index) const {
1059  update_();
1060 
1061  if (index >= quad_list_.size())
1062  throw gui::exception("text", "Trying to access letter at invalid index.");
1063 
1064  return quad_list_[index];
1065 }
1066 
1067 } // namespace lxgui::gui
Holds a single color (float RGBA, 128 bits)
Definition: gui_color.hpp:12
static const color white
Definition: gui_color.hpp:43
static const color black
Definition: gui_color.hpp:44
static const color empty
Definition: gui_color.hpp:42
Exception to be thrown by GUI code.
Abstract type for implementation specific management.
virtual std::shared_ptr< vertex_cache > create_vertex_cache(gui::vertex_cache::type type)=0
Creates a new empty vertex cache.
void render_quad(const quad &q)
Renders a quad.
void render_cache(const material *mat, const vertex_cache &cache, const matrix4f &model_transform=matrix4f::identity)
Renders a vertex cache.
virtual bool is_vertex_cache_supported() const =0
Checks if the renderer supports vertex caches.
void render_quads(const material *mat, const std::vector< std::array< vertex, 4 >> &quad_list)
Renders a set of quads.
float get_alpha() const
Returns this text's transparency (alpha).
Definition: gui_text.cpp:342
bool is_word_ellipsis_enabled() const
Checks if word ellipsis is enabled.
Definition: gui_text.cpp:523
quad create_letter_quad(char32_t c) const
Creates a quad that contains the provided character.
Definition: gui_text.cpp:1045
float get_line_spacing() const
Returns this text's line spacing.
Definition: gui_text.cpp:484
void render(const matrix4f &transform=matrix4f::identity) const
Renders this text at the given position.
Definition: gui_text.cpp:548
alignment_y get_alignment_y() const
Returns the text vertical alignment.
Definition: gui_text.cpp:458
float get_box_width() const
Returns the width of the text box.
Definition: gui_text.cpp:384
void set_remove_starting_spaces(bool remove_starting_spaces)
Allows removal of a line's starting spaces.
Definition: gui_text.cpp:488
float get_character_kerning(char32_t c1, char32_t c2) const
Returns the kerning between two characters.
Definition: gui_text.cpp:432
void set_word_ellipsis_enabled(bool add_ellipsis)
Sets whether to show an ellipsis "..." if words don't fit in the text box.
Definition: gui_text.cpp:514
void set_use_vertex_cache(bool use_vertex_cache)
Sets whether this text object should use vertex caches or not.
Definition: gui_text.cpp:536
bool get_remove_starting_spaces() const
Checks if starting spaces removing is active.
Definition: gui_text.cpp:497
bool get_use_vertex_cache() const
Checks if this text object is using vertex cache or not.
Definition: gui_text.cpp:540
float get_string_width(const std::string &content) const
Returns the length of a provided string.
Definition: gui_text.cpp:411
void set_tracking(float tracking)
Sets this text's tracking.
Definition: gui_text.cpp:462
float get_width() const
Returns the width of the rendered text.
Definition: gui_text.cpp:374
void set_box_height(float box_height)
Sets the height of the text box.
Definition: gui_text.cpp:365
void set_scaling_factor(float scaling_factor)
Set the scaling factor to use when rendering glyphs.
Definition: gui_text.cpp:294
alignment_x get_alignment_x() const
Returns the text horizontal alignment.
Definition: gui_text.cpp:454
float get_text_height() const
Returns the height of the text.
Definition: gui_text.cpp:396
const std::array< vertex, 4 > & get_letter_quad(std::size_t index) const
Returns the quad for the letter at the provided index (position, texture coords, color).
Definition: gui_text.cpp:1058
float get_text_width() const
Returns the length of the text.
Definition: gui_text.cpp:392
void set_color(const color &c, bool force_color=false)
Sets this text's default color.
Definition: gui_text.cpp:319
void set_alpha(float alpha)
Sets this text's transparency (alpha).
Definition: gui_text.cpp:333
void set_alignment_y(alignment_y align_y)
Sets text vertical alignment.
Definition: gui_text.cpp:445
float get_height() const
Returns the height of the rendered text.
Definition: gui_text.cpp:379
void set_alignment_x(alignment_x align_x)
Sets text horizontal alignment.
Definition: gui_text.cpp:436
void set_box_dimensions(float box_width, float box_height)
Sets the dimensions of the text box.
Definition: gui_text.cpp:346
std::size_t get_line_count() const
Returns the number of text lines.
Definition: gui_text.cpp:406
void set_word_wrap_enabled(bool wrap)
Allows/disallows word wrap when the line is too long for the text box.
Definition: gui_text.cpp:501
float get_box_height() const
Returns the height of the text box.
Definition: gui_text.cpp:388
void set_formatting_enabled(bool formatting)
Enables color formatting.
Definition: gui_text.cpp:527
const color & get_color() const
Returns this text's default color.
Definition: gui_text.cpp:329
float get_tracking() const
Returns this text's tracking.
Definition: gui_text.cpp:471
float get_line_height() const
Returns the height of one line (constant).
Definition: gui_text.cpp:287
void set_text(const utils::ustring &content)
Sets the text to render (unicode character set).
Definition: gui_text.cpp:306
void set_line_spacing(float line_spacing)
Sets this text's line spacing.
Definition: gui_text.cpp:475
float get_scaling_factor() const
Returns the scaling factor used when rendering glyphs.
Definition: gui_text.cpp:302
const utils::ustring & get_text() const
Returns the text that will be rendered (unicode character set).
Definition: gui_text.cpp:315
text(renderer &rdr, std::shared_ptr< const font > fnt, std::shared_ptr< const font > outline_fnt=nullptr)
Constructor.
Definition: gui_text.cpp:283
float get_character_width(char32_t c) const
Returns the length of a single character.
Definition: gui_text.cpp:423
std::size_t get_letter_count() const
Returns the number of letters currently displayed.
Definition: gui_text.cpp:1053
void set_box_width(float box_width)
Sets the width of the text box.
Definition: gui_text.cpp:356
bool is_word_wrap_enabled() const
Checks if word wrap is enabled.
Definition: gui_text.cpp:510
@ quads
3 vertices per element
vector2< float > vector2f
Holds 2D coordinates (as floats)
bounds2< float > bounds2f
Holds 2D bounds of a region (as floats).
range_impl::iterator_range< T > iterator(T &container)
Expose the iterator rather than the element.
range_impl::value_range< T > value(T &container)
Expose the value rather than the (key,value) pair.
float round(float value, float unit, rounding_method method)
Round a floating point value to a specific unit and using a specific rounding method.
Definition: utils_maths.cpp:8
rounding_method
Rounding method for points to pixels conversions.
Definition: utils_maths.hpp:10
vector2< T > top_left() const noexcept
Definition: gui_bounds2.hpp:32
A 4x4 matrix, used for coordinate transformations.
Definition: gui_matrix4.hpp:13
Simple structure holding four vertices and a material.
Definition: gui_quad.hpp:18
std::shared_ptr< material > mat
Definition: gui_quad.hpp:20
std::array< vertex, 4 > v
Definition: gui_quad.hpp:19