1 ///////////////////////////////////////////////////////////////////////////////
2 // Copyright (C) 2004-2011 by The Allacrost Project
3 // Copyright (C) 2012-2016 by Bertram (Valyria Tear)
4 // All Rights Reserved
5 //
6 // This code is licensed under the GNU GPL version 2. It is free software
7 // and you may modify it and/or redistribute it under the terms of this license.
8 // See https://www.gnu.org/copyleft/gpl.html for details.
9 ///////////////////////////////////////////////////////////////////////////////
10
11 /** ****************************************************************************
12 *** \file text.cpp
13 *** \author Lindsay Roberts, linds@allacrost.org
14 *** \author Yohann Ferreira, yohann ferreira orange fr
15 *** \brief Source file for text rendering
16 ***
17 *** This code makes use of the SDL_ttf font library for representing fonts,
18 *** font glyphs, and text.
19 ***
20 *** \note Normally the int data type should not be used in the game code,
21 *** however it is used periodically throughout this file as the SDL_ttf library
22 *** requests integer arguments.
23 *** ***************************************************************************/
24
25 #include "text.h"
26 #include "video.h"
27
28 #include "script/script_read.h"
29 #include "engine/system.h"
30
31 #ifdef __APPLE__
32 # include <SDL_ttf.h>
33 #else
34 # include <SDL2/SDL_ttf.h>
35 #endif
36
37 // The script filename used to configure the text styles used in game.
38 const std::string _font_script_filename = "data/config/fonts.lua";
39
40 using namespace vt_utils;
41 using namespace vt_video::private_video;
42
43 namespace vt_video
44 {
45
46 TextSupervisor *TextManager = nullptr;
47
48 // Useful character types for text formatting
49 const uint16_t NEW_LINE = '\n';
50 const uint16_t SPACE_CHAR = 0x20;
51
52 // -----------------------------------------------------------------------------
53 // FontProperties class
54 // -----------------------------------------------------------------------------
55
FontProperties()56 FontProperties::FontProperties() :
57 height(0),
58 line_skip(0),
59 ascent(0),
60 descent(0),
61 ttf_font(nullptr),
62 font_size(0)
63 {
64 }
65
~FontProperties()66 FontProperties::~FontProperties()
67 {
68 ClearFont();
69 }
70
ClearFont()71 void FontProperties::ClearFont()
72 {
73 // Free the font.
74 if (ttf_font)
75 TTF_CloseFont(ttf_font);
76
77 ttf_font = nullptr;
78 }
79
FontProperties(const FontProperties &)80 FontProperties::FontProperties(const FontProperties&)
81 {
82 throw Exception("Not Implemented!", __FILE__, __LINE__, __FUNCTION__);
83 }
84
operator =(const FontProperties &)85 FontProperties& FontProperties::operator=(const FontProperties&)
86 {
87 throw Exception("Not Implemented!", __FILE__, __LINE__, __FUNCTION__);
88 return *this;
89 }
90
91 // -----------------------------------------------------------------------------
92 // TextStyle class
93 // -----------------------------------------------------------------------------
94
TextStyle(const std::string & font)95 TextStyle::TextStyle(const std::string& font)
96 {
97 const TextStyle& default_style = TextManager->GetDefaultStyle();
98 _font = font;
99 _color = default_style.GetColor();
100 _shadow_style = default_style.GetShadowStyle();
101 _shadow_offset_x = default_style.GetShadowOffsetX();
102 _shadow_offset_y = default_style.GetShadowOffsetY();
103 _font_property = TextManager->_GetFontProperties(_font);
104 _UpdateTextShadowColor();
105 }
106
TextStyle(const Color & color)107 TextStyle::TextStyle(const Color& color)
108 {
109 const TextStyle& default_style = TextManager->GetDefaultStyle();
110 _font = default_style.GetFontName();
111 _color = color;
112 _shadow_style = default_style.GetShadowStyle();
113 _shadow_offset_x = default_style.GetShadowOffsetX();
114 _shadow_offset_y = default_style.GetShadowOffsetY();
115 _font_property = TextManager->_GetFontProperties(_font);
116 _UpdateTextShadowColor();
117 }
118
TextStyle(TEXT_SHADOW_STYLE style)119 TextStyle::TextStyle(TEXT_SHADOW_STYLE style)
120 {
121 const TextStyle& default_style = TextManager->GetDefaultStyle();
122 _font = default_style.GetFontName();
123 _color = default_style.GetColor();
124 _shadow_style = style;
125 _shadow_offset_x = default_style.GetShadowOffsetX();
126 _shadow_offset_y = default_style.GetShadowOffsetY();
127 _font_property = TextManager->_GetFontProperties(_font);
128 _UpdateTextShadowColor();
129 }
130
TextStyle(const std::string & font,const Color & color)131 TextStyle::TextStyle(const std::string& font, const Color& color)
132 {
133 const TextStyle& default_style = TextManager->GetDefaultStyle();
134 _font = font;
135 _color = color;
136 _shadow_style = default_style.GetShadowStyle();
137 _shadow_offset_x = default_style.GetShadowOffsetX();
138 _shadow_offset_y = default_style.GetShadowOffsetY();
139 _font_property = TextManager->_GetFontProperties(_font);
140 _UpdateTextShadowColor();
141 }
142
TextStyle(const std::string & font,TEXT_SHADOW_STYLE style)143 TextStyle::TextStyle(const std::string& font, TEXT_SHADOW_STYLE style)
144 {
145 const TextStyle& default_style = TextManager->GetDefaultStyle();
146 _font = font;
147 _color = default_style.GetColor();
148 _shadow_style = style;
149 _shadow_offset_x = default_style.GetShadowOffsetX();
150 _shadow_offset_y = default_style.GetShadowOffsetY();
151 _font_property = TextManager->_GetFontProperties(_font);
152 _UpdateTextShadowColor();
153 }
154
TextStyle(const Color & color,TEXT_SHADOW_STYLE style)155 TextStyle::TextStyle(const Color& color, TEXT_SHADOW_STYLE style)
156 {
157 const TextStyle& default_style = TextManager->GetDefaultStyle();
158 _font = default_style.GetFontName();
159 _color = color;
160 _shadow_style = style;
161 _shadow_offset_x = default_style.GetShadowOffsetX();
162 _shadow_offset_y = default_style.GetShadowOffsetY();
163 _font_property = TextManager->_GetFontProperties(_font);
164 _UpdateTextShadowColor();
165 }
166
TextStyle(const std::string & font,const Color & color,TEXT_SHADOW_STYLE style)167 TextStyle::TextStyle(const std::string& font,
168 const Color& color,
169 TEXT_SHADOW_STYLE style)
170 {
171 const TextStyle& default_style = TextManager->GetDefaultStyle();
172 _font = font;
173 _color = color;
174 _shadow_style = style;
175 _shadow_offset_x = default_style.GetShadowOffsetX();
176 _shadow_offset_y = default_style.GetShadowOffsetY();
177 _font_property = TextManager->_GetFontProperties(_font);
178 _UpdateTextShadowColor();
179 }
180
TextStyle(const std::string & font,const Color & color,TEXT_SHADOW_STYLE style,int32_t shadow_x,int32_t shadow_y)181 TextStyle::TextStyle(const std::string& font,
182 const Color& color,
183 TEXT_SHADOW_STYLE style,
184 int32_t shadow_x,
185 int32_t shadow_y)
186 {
187 _font = font;
188 _color = color;
189 _shadow_style = style;
190 _shadow_offset_x = shadow_x;
191 _shadow_offset_y = shadow_y;
192 _font_property = TextManager->_GetFontProperties(_font);
193 _UpdateTextShadowColor();
194 }
195
SetFont(const std::string & font)196 void TextStyle::SetFont(const std::string& font)
197 {
198 _font = font;
199 _font_property = TextManager->_GetFontProperties(font);
200 }
201
_UpdateTextShadowColor()202 void TextStyle::_UpdateTextShadowColor()
203 {
204 switch(_shadow_style) {
205 default:
206 case VIDEO_TEXT_SHADOW_NONE:
207 _shadow_color = Color::clear;
208 break;
209 case VIDEO_TEXT_SHADOW_DARK:
210 _shadow_color = Color::black;
211 _shadow_color[3] = _color[3] * 0.5f;
212 break;
213 case VIDEO_TEXT_SHADOW_LIGHT:
214 _shadow_color = Color::white;
215 _shadow_color[3] = _color[3] * 0.5f;
216 break;
217 case VIDEO_TEXT_SHADOW_BLACK:
218 _shadow_color = Color::black;
219 _shadow_color[3] = _color[3];
220 break;
221 case VIDEO_TEXT_SHADOW_COLOR:
222 _shadow_color = _color;
223 _shadow_color[3] = _color[3] * 0.5f;
224 break;
225 case VIDEO_TEXT_SHADOW_INVCOLOR:
226 _shadow_color = Color(1.0f - _color[0], 1.0f - _color[1],
227 1.0f - _color[2], _color[3] * 0.5f);
228 break;
229 }
230 }
231
232 namespace private_video
233 {
234
235 // -----------------------------------------------------------------------------
236 // TextTexture class
237 // -----------------------------------------------------------------------------
238
TextTexture(const vt_utils::ustring & string_,const TextStyle & style_)239 TextTexture::TextTexture(const vt_utils::ustring &string_,
240 const TextStyle &style_) :
241 BaseTexture(),
242 string(string_),
243 style(style_)
244 {
245 // Enable image smoothing for text
246 smooth = true;
247 }
248
~TextTexture()249 TextTexture::~TextTexture()
250 {
251 // Remove this instance from the texture manager
252 TextureManager->_UnregisterTextTexture(this);
253 }
254
Regenerate()255 bool TextTexture::Regenerate()
256 {
257 if(texture_sheet) {
258 texture_sheet->RemoveTexture(this);
259 TextureManager->_RemoveSheet(texture_sheet);
260 texture_sheet = nullptr;
261 }
262
263 ImageMemory buffer;
264 if(!TextManager->_RenderText(string, style, buffer))
265 return false;
266
267 width = buffer.GetWidth();
268 height = buffer.GetHeight();
269
270 TexSheet *sheet =
271 TextureManager->_InsertImageInTexSheet(this, buffer, true);
272 if(sheet == nullptr) {
273 IF_PRINT_WARNING(VIDEO_DEBUG) << "Call to TextureManager::_InsertImageInTexSheet() returned nullptr" << std::endl;
274 return false;
275 }
276
277 texture_sheet = sheet;
278 return true;
279 }
280
Reload()281 bool TextTexture::Reload()
282 {
283 // Regenerate text image if it is not already loaded in a texture sheet
284 if(texture_sheet == nullptr)
285 return Regenerate();
286
287 ImageMemory buffer;
288 if(!TextManager->_RenderText(string, style, buffer))
289 return false;
290
291 if(!texture_sheet->CopyRect(x, y, buffer)) {
292 IF_PRINT_WARNING(VIDEO_DEBUG) << "Call to TextureSheet::CopyRect() failed" << std::endl;
293 return false;
294 }
295
296 return true;
297 }
298
299 // -----------------------------------------------------------------------------
300 // TextElement class
301 // -----------------------------------------------------------------------------
302
TextElement()303 TextElement::TextElement() :
304 ImageDescriptor(),
305 text_texture(nullptr)
306 {}
307
TextElement(TextTexture * texture)308 TextElement::TextElement(TextTexture *texture) :
309 ImageDescriptor(),
310 text_texture(texture)
311 {
312 SetTexture(texture);
313 }
314
~TextElement()315 TextElement::~TextElement()
316 {
317 Clear();
318 }
319
Clear()320 void TextElement::Clear()
321 {
322 ImageDescriptor::Clear(); // This call will remove the texture reference for us
323 text_texture = nullptr;
324 }
325
Draw() const326 void TextElement::Draw() const
327 {
328 Draw(Color::white);
329 }
330
Draw(const Color & draw_color) const331 void TextElement::Draw(const Color &draw_color) const
332 {
333 // Don't draw anything if this image is completely transparent (invisible).
334 if (IsFloatEqual(draw_color[3], 0.0f))
335 return;
336
337 VideoManager->PushMatrix();
338 _DrawOrientation();
339
340 if(draw_color == Color::white) {
341 _DrawTexture(_color);
342 } else {
343 Color modulated_colors[4];
344 modulated_colors[0] = _color[0] * draw_color;
345 modulated_colors[1] = _color[1] * draw_color;
346 modulated_colors[2] = _color[2] * draw_color;
347 modulated_colors[3] = _color[3] * draw_color;
348
349 _DrawTexture(modulated_colors);
350 }
351
352 VideoManager->PopMatrix();
353 }
354
SetTexture(TextTexture * texture)355 void TextElement::SetTexture(TextTexture *texture)
356 {
357 // Do nothing if the texture pointer is not going to change
358 if(text_texture == texture) {
359 return;
360 }
361
362 // Remove references and possibly delete the existing texture
363 if(text_texture != nullptr) {
364 _RemoveTextureReference();
365
366 text_texture = nullptr;
367 _texture = nullptr;
368 }
369
370 // Set the new texture
371 if(texture == nullptr) {
372 text_texture = nullptr;
373 _texture = nullptr;
374 _width = 0.0f;
375 _height = 0.0f;
376 } else {
377 texture->AddReference();
378 text_texture = texture;
379 _texture = texture;
380
381 _width = static_cast<float>(texture->width);
382 _height = static_cast<float>(texture->height);
383 }
384 }
385
386 } // namespace private_video
387
388 // -----------------------------------------------------------------------------
389 // TextImage class
390 // -----------------------------------------------------------------------------
391
TextImage()392 TextImage::TextImage() :
393 ImageDescriptor(),
394 _style(TextManager->GetDefaultStyle()),
395 _max_width(vt_video::VIDEO_STANDARD_RES_WIDTH)
396 {
397 }
398
TextImage(const ustring & text,const TextStyle & style)399 TextImage::TextImage(const ustring& text, const TextStyle& style) :
400 ImageDescriptor(),
401 _text(text),
402 _style(style),
403 _max_width(vt_video::VIDEO_STANDARD_RES_WIDTH)
404 {
405 _Regenerate();
406 }
407
TextImage(const std::string & text,const TextStyle & style)408 TextImage::TextImage(const std::string& text, const TextStyle& style) :
409 ImageDescriptor(),
410 _text(MakeUnicodeString(text)),
411 _style(style),
412 _max_width(vt_video::VIDEO_STANDARD_RES_WIDTH)
413 {
414 _Regenerate();
415 }
416
TextImage(const TextImage & copy)417 TextImage::TextImage(const TextImage ©) :
418 ImageDescriptor(copy),
419 _text(copy._text),
420 _style(copy._style),
421 _max_width(copy._max_width)
422 {
423 for(uint32_t i = 0; i < copy._text_sections.size(); i++) {
424 _text_sections.push_back(new TextElement(*(copy._text_sections[i])));
425 }
426 }
427
operator =(const TextImage & copy)428 TextImage &TextImage::operator=(const TextImage ©)
429 {
430 // Prevents object assignment to itself
431 if(this == ©)
432 return *this;
433
434 // Remove references to any existing text sections
435 for(uint32_t i = 0; i < _text_sections.size(); ++i)
436 delete _text_sections[i];
437
438 _text_sections.clear();
439
440 _text = copy._text;
441 _style = copy._style;
442 _max_width = copy._max_width;
443 for(uint32_t i = 0; i < copy._text_sections.size(); ++i)
444 _text_sections.push_back(new TextElement(*(copy._text_sections[i])));
445
446 return *this;
447 }
448
Clear()449 void TextImage::Clear()
450 {
451 ImageDescriptor::Clear();
452 _text.clear();
453 for(uint32_t i = 0; i < _text_sections.size(); ++i)
454 delete _text_sections[i];
455
456 _text_sections.clear();
457 _width = 0;
458 _height = 0;
459 // Don't reset the max width as the normal flow might want a new text again
460 // with the same constraints.
461 }
462
Draw(const Color & draw_color) const463 void TextImage::Draw(const Color& draw_color) const
464 {
465 // Don't draw anything if this image is completely transparent (invisible).
466 if (IsFloatEqual(draw_color[3], 0.0f))
467 return;
468
469 // Save the draw cursor position before drawing this text.
470 VideoManager->PushMatrix();
471
472 for (uint32_t i = 0; i < _text_sections.size(); ++i) {
473 if (_style.GetShadowStyle() != VIDEO_TEXT_SHADOW_NONE) {
474 // Draw the text's shadow.
475 const float dx = VideoManager->_current_context.coordinate_system.GetHorizontalDirection() * _style.GetShadowOffsetX();
476 const float dy = VideoManager->_current_context.coordinate_system.GetVerticalDirection() * _style.GetShadowOffsetY();
477 VideoManager->MoveRelative(dx, dy);
478 _text_sections[i]->Draw(draw_color * _style.GetShadowColor());
479 VideoManager->MoveRelative(-dx, -dy);
480 }
481
482 // Draw the text.
483 _text_sections[i]->Draw(draw_color * _style.GetColor());
484
485 // Move the draw cursor one line down.
486 VideoManager->MoveRelative(0.0f, _style.GetFontProperties()->line_skip * -VideoManager->_current_context.coordinate_system.GetVerticalDirection());
487 }
488
489 // Restore the position of the draw cursor.
490 VideoManager->PopMatrix();
491 }
492
SetWordWrapWidth(uint32_t width)493 void TextImage::SetWordWrapWidth(uint32_t width)
494 {
495 if (_max_width == width)
496 return;
497 _max_width = width;
498 _Regenerate();
499 }
500
_Regenerate()501 void TextImage::_Regenerate()
502 {
503 _width = 0.0f;
504 _height = 0.0f;
505
506 for (uint32_t i = 0; i < _text_sections.size(); ++i)
507 delete _text_sections[i];
508
509 _text_sections.clear();
510
511 if(_text.empty())
512 return;
513
514 FontProperties* fp = _style.GetFontProperties();
515 if (fp == nullptr || fp->ttf_font == nullptr) {
516 IF_PRINT_WARNING(VIDEO_DEBUG) << "invalid font or font properties"
517 << std::endl;
518 return;
519 }
520
521 // Iterate through each line of text and render a text texture for each one.
522 std::vector<ustring> lines_array = TextManager->WrapText(_text, fp->ttf_font, _max_width);
523 std::vector<ustring>::iterator line_iter;
524 for(line_iter = lines_array.begin(); line_iter != lines_array.end(); ++line_iter) {
525
526 TextElement *new_element = new TextElement();
527 // If this line is only a newline character or is an empty string, create an empty TextElement object
528 if((*line_iter) == ustring(&NEW_LINE) || (*line_iter).empty()) {
529 new_element->SetDimensions(0.0f, static_cast<float>(fp->line_skip));
530 }
531 // Otherwise, create a new TextTexture to be managed by the new element
532 else {
533 // PRINT_DEBUG << **line_iter << std::endl;
534 TextTexture *texture = new TextTexture(*line_iter, _style);
535 if(texture->Regenerate() == false) {
536 IF_PRINT_WARNING(VIDEO_DEBUG) << "call to TextTexture::_Regenerate() failed" << std::endl;
537 }
538 TextureManager->_RegisterTextTexture(texture);
539
540 // Resize the TextImage width if this line is wider than the current width
541 if(texture->width > _width)
542 _width = static_cast<float>(texture->width);
543
544 new_element->SetTexture(texture); // Automatically adds a reference to texture
545 }
546 _text_sections.push_back(new_element);
547
548 // Increase height by the font specified line height
549 _height += fp->line_skip;
550 }
551 } // void TextImage::_Regenerate()
552
553 // -----------------------------------------------------------------------------
554 // TextSupervisor class
555 // -----------------------------------------------------------------------------
556
TextSupervisor()557 TextSupervisor::TextSupervisor() :
558 _text_texture(0),
559 _text_texture_width(0),
560 _text_texture_height(0)
561 {
562 glGenTextures(1, &_text_texture);
563 if (_text_texture == 0) {
564 IF_PRINT_WARNING(VIDEO_DEBUG) << "call to glGenTextures() failed" << std::endl;
565 assert(_text_texture != 0);
566 }
567 }
568
~TextSupervisor()569 TextSupervisor::~TextSupervisor()
570 {
571 // Clean up the text texture.
572 if (_text_texture != 0) {
573 GLuint textures[] = { _text_texture };
574 glDeleteTextures(1, textures);
575 _text_texture = 0;
576 }
577
578 // Remove all loaded fonts. Then, shutdown the SDL_ttf library.
579 for (auto it = _font_map.begin(); it != _font_map.end(); ++it)
580 delete it->second;
581
582 TTF_Quit();
583 }
584
SingletonInitialize()585 bool TextSupervisor::SingletonInitialize()
586 {
587 if(TTF_Init() < 0) {
588 PRINT_ERROR << "SDL_ttf initialization failed" << std::endl;
589 return false;
590 }
591
592 return true;
593 }
594
LoadFonts(const std::string & locale_name)595 bool TextSupervisor::LoadFonts(const std::string& locale_name)
596 {
597 vt_script::ReadScriptDescriptor font_script;
598
599 //Checking the file existence and validity.
600 if(!font_script.OpenFile(_font_script_filename)) {
601 PRINT_ERROR << "Couldn't open font file: "
602 << _font_script_filename << std::endl;
603 return false;
604 }
605
606 if(!font_script.DoesTableExist("fonts")) {
607 PRINT_ERROR << "No 'fonts' table in file: "
608 << _font_script_filename << std::endl;
609 font_script.CloseFile();
610 return false;
611 }
612
613 std::vector<std::string> locale_names;
614 font_script.ReadTableKeys("fonts", locale_names);
615 if(locale_names.empty() || !font_script.OpenTable("fonts")) {
616 PRINT_ERROR << "No local array defined in the 'fonts' table of file: "
617 << _font_script_filename << std::endl;
618 font_script.CloseFile();
619 return false;
620 }
621
622 std::string style_default = font_script.ReadString("font_default_style");
623 if(style_default.empty()) {
624 PRINT_ERROR << "No default text style defined in: "
625 << _font_script_filename
626 << std::endl;
627 font_script.CloseFile();
628 return false;
629 }
630
631 // Search for a 'default' array and the specific locale array
632 bool default_locale_array_found = false;
633 bool specific_locale_array_found = false;
634
635 // We only keep the array we need:
636 // the default one and the locale specific one.
637 for(uint32_t j = 0; j < locale_names.size(); ++j) {
638 std::string locale = locale_names[j];
639 // Keep the default array
640 if (!strcasecmp(locale.c_str(), "default")) {
641 default_locale_array_found = true;
642 continue;
643 }
644
645 if (locale_name.empty())
646 continue;
647
648 if (!strcasecmp(locale.c_str(), locale_name.c_str()))
649 specific_locale_array_found = true;
650 }
651
652 // If there is no default arrays. Exit now as the script file is invalid.
653 if (!default_locale_array_found) {
654 PRINT_ERROR << "Can't load fonts. No 'default' local array found in file: " << _font_script_filename << std::endl;
655 font_script.CloseFile();
656 return false;
657 }
658
659 // We set the arrays we want to parse.
660 locale_names.clear();
661 // The default one must come in first to permit locale specific fonts to override them.
662 locale_names.push_back("default");
663 if (specific_locale_array_found)
664 locale_names.push_back(locale_name);
665
666 // We now parse the wanted tables only, and the (re)load the fonts accordingly.
667 for(uint32_t j = 0; j < locale_names.size(); ++j) {
668 std::string locale = locale_names[j];
669
670 std::vector<std::string> style_names;
671 font_script.ReadTableKeys(locale, style_names);
672 if(style_names.empty()) {
673 PRINT_ERROR << "No text styles defined in the table '"<< locale << "' of file: "
674 << _font_script_filename << std::endl;
675 font_script.CloseFile();
676 return false;
677 }
678
679 if (!font_script.OpenTable(locale)) { // locale
680 PRINT_ERROR << "Can't open locale table '"<< locale << "' of file: "
681 << _font_script_filename << std::endl;
682 font_script.CloseFile();
683 return false;
684 }
685
686 for(uint32_t i = 0; i < style_names.size(); ++i) {
687
688 if (!font_script.OpenTable(style_names[i])) { // Text style
689 PRINT_ERROR << "Can't open text style table '" << style_names[i]
690 << "' of locale: '" << locale << "' in file: "
691 << _font_script_filename << std::endl;
692 font_script.CloseFile();
693 continue;
694 }
695
696 std::string font_file = font_script.ReadString("font");
697 uint32_t font_size = font_script.ReadInt("size");
698
699 if(!_LoadFont(style_names[i], font_file, font_size)) {
700 // Check whether the default font is invalid
701 if(style_default == style_names[i]) {
702 font_script.CloseAllTables();
703 font_script.CloseFile();
704 PRINT_ERROR << "The default text style '" << style_default
705 << "' couldn't be loaded in file: "
706 << _font_script_filename
707 << std::endl;
708 return false;
709 }
710 else {
711 PRINT_WARNING << "The text style '" << style_names[i]
712 << "' couldn't be loaded in file: "
713 << _font_script_filename
714 << std::endl;
715 }
716 }
717
718 font_script.CloseTable(); // Text style
719 } // load each TextStyle
720
721 font_script.CloseTable(); // locale
722 }
723 font_script.CloseTable(); // fonts
724
725 font_script.CloseFile();
726
727 // Setup the default font
728 SetDefaultStyle(TextStyle(style_default, Color::white, VIDEO_TEXT_SHADOW_BLACK, 1, -2));
729 return true;
730 }
731
_LoadFont(const std::string & textstyle_name,const std::string & font_filename,uint32_t font_size)732 bool TextSupervisor::_LoadFont(const std::string& textstyle_name,
733 const std::string& font_filename,
734 uint32_t font_size)
735 {
736 if(font_size == 0) {
737 PRINT_ERROR << "Attempted to load a text style of size zero: "
738 << textstyle_name << std::endl;
739 return false;
740 }
741
742 // Check whether the TextStyle name is not already taken
743 bool reload = false;
744 auto it = _font_map.find(textstyle_name);
745 if(it != _font_map.end()) {
746 reload = true;
747
748 // Let's check whether the requested font is exactly the same than before
749 // and do nothing in this case so we don't hurt performance.
750 FontProperties *fp = it->second;
751 if (fp && fp->font_filename == font_filename && fp->font_size == font_size)
752 return true;
753 }
754
755 // Attempt to load the font
756 TTF_Font *font = TTF_OpenFont(font_filename.c_str(), font_size);
757 if(font == nullptr) {
758 PRINT_ERROR << "Call to TTF_OpenFont() failed to load the font file: "
759 << font_filename << std::endl
760 << TTF_GetError() << std::endl;
761 return false;
762 }
763
764 // Get or Create a new FontProperties object for this font and set all of the properties according to SDL_ttf
765 FontProperties* fp = reload ? it->second : new FontProperties();
766
767 if (fp == nullptr) {
768 PRINT_ERROR << "Invalid Font Properties instance for text style: "
769 << textstyle_name << std::endl;
770 return false;
771 }
772
773 // We first clear the font before setting a new one in case of a reload.
774 if (reload)
775 fp->ClearFont();
776
777 fp->ttf_font = font;
778 fp->font_filename = font_filename;
779 fp->font_size = font_size;
780 fp->height = TTF_FontHeight(font);
781 fp->line_skip = TTF_FontLineSkip(font);
782 fp->ascent = TTF_FontAscent(font);
783 fp->descent = TTF_FontDescent(font);
784
785 // If the text style is new, we add it to the font cache map
786 if (!reload)
787 _font_map[textstyle_name] = fp;
788
789 return true;
790 }
791
_FreeFont(const std::string & font_name)792 void TextSupervisor::_FreeFont(const std::string &font_name)
793 {
794 auto it = _font_map.find(font_name);
795 if(it == _font_map.end()) {
796 IF_PRINT_WARNING(VIDEO_DEBUG) << "argument font name was invalid: "
797 << font_name << std::endl;
798 return;
799 }
800
801 // Free the font and remove it from the font cache
802 delete it->second;
803
804 // Remove the data from the map once freed.
805 _font_map.erase(it);
806 }
807
_GetFontProperties(const std::string & font_name)808 FontProperties *TextSupervisor::_GetFontProperties(const std::string &font_name)
809 {
810 if(_IsFontValid(font_name) == false) {
811 IF_PRINT_WARNING(VIDEO_DEBUG) << "argument font name was invalid: "
812 << font_name << std::endl;
813 return nullptr;
814 }
815
816 return _font_map[font_name];
817 }
818
Draw(const ustring & text,const TextStyle & style)819 void TextSupervisor::Draw(const ustring &text, const TextStyle &style)
820 {
821 if (text.empty()) {
822 IF_PRINT_WARNING(VIDEO_DEBUG) << "empty string was passed to function" << std::endl;
823 return;
824 }
825
826 FontProperties *fp = style.GetFontProperties();
827 if (fp == nullptr || fp->ttf_font == nullptr) {
828 IF_PRINT_WARNING(VIDEO_DEBUG) << "failed because font was invalid: " << style.GetFontName() << std::endl;
829 return;
830 }
831
832 VideoManager->PushState();
833
834 // Break the string into lines and render the shadow and text for each line
835 uint16_t buffer[2048];
836 size_t last_line = 0;
837 do {
838 // Find the next new line character in the string and save the line
839 size_t next_line;
840 for(next_line = last_line; next_line < text.length(); next_line++) {
841 if(text[next_line] == NEW_LINE)
842 break;
843
844 buffer[next_line - last_line] = text[next_line];
845 }
846 buffer[next_line - last_line] = 0;
847 last_line = next_line + 1;
848
849 // If this line is empty, skip on to the next one
850 if(buffer[0] == 0) {
851 VideoManager->MoveRelative(0, -fp->line_skip * VideoManager->_current_context.coordinate_system.GetVerticalDirection());
852 continue;
853 }
854
855 // Save the draw cursor position before drawing this text.
856 VideoManager->PushMatrix();
857
858 // If text shadows are enabled...
859 if (style.GetShadowStyle() != VIDEO_TEXT_SHADOW_NONE) {
860 // Draw the text with its shadow.
861 _RenderText(buffer, fp, style.GetColor(), style.GetShadowOffsetX(), style.GetShadowOffsetY(), style.GetShadowColor());
862 } else {
863 // Draw the text.
864 _RenderText(buffer, fp, style.GetColor());
865 }
866
867 // Restore the position of the draw cursor.
868 VideoManager->PopMatrix();
869
870 // Move the draw cursor one line down.
871 VideoManager->MoveRelative(0, -fp->line_skip * VideoManager->_current_context.coordinate_system.GetVerticalDirection());
872
873 } while (last_line < text.length());
874
875 VideoManager->PopState();
876 }
877
CalculateTextWidth(TTF_Font * ttf_font,const vt_utils::ustring & text)878 int32_t TextSupervisor::CalculateTextWidth(TTF_Font* ttf_font, const vt_utils::ustring &text)
879 {
880 if(ttf_font == nullptr) {
881 IF_PRINT_WARNING(VIDEO_DEBUG) << "Invalid font" << std::endl;
882 return -1;
883 }
884
885 int32_t width;
886 if(TTF_SizeUNICODE(ttf_font, text.c_str(), &width, nullptr) == -1) {
887 IF_PRINT_WARNING(VIDEO_DEBUG) << "Call to TTF_SizeUNICODE failed with TTF error: " << TTF_GetError() << std::endl;
888 return -1;
889 }
890
891 return width;
892 }
893
CalculateTextWidth(TTF_Font * ttf_font,const std::string & text)894 int32_t TextSupervisor::CalculateTextWidth(TTF_Font* ttf_font, const std::string &text)
895 {
896 if(ttf_font == nullptr) {
897 IF_PRINT_WARNING(VIDEO_DEBUG) << "Invalid font" << std::endl;
898 return -1;
899 }
900
901 int32_t width;
902 if(TTF_SizeText(ttf_font, text.c_str(), &width, nullptr) == -1) {
903 IF_PRINT_WARNING(VIDEO_DEBUG) << "Call to TTF_SizeText failed with TTF error: " << TTF_GetError() << std::endl;
904 return -1;
905 }
906
907 return width;
908 }
909
WrapText(const vt_utils::ustring & text,TTF_Font * ttf_font,uint32_t max_width)910 std::vector<vt_utils::ustring> TextSupervisor::WrapText(const vt_utils::ustring& text,
911 TTF_Font* ttf_font,
912 uint32_t max_width)
913 {
914 std::vector<vt_utils::ustring> lines_array;
915 if (text.empty() || max_width == 0) {
916 // This can happen when called with uninit // gui objects.
917 return lines_array;
918 }
919
920 // We split the text using new lines in a first row
921 ustring temp_text = text;
922 uint32_t text_length = temp_text.length();
923
924 for (uint32_t i = 0; i < text_length; ++i) {
925 if(!(temp_text[i] == NEW_LINE))
926 continue;
927
928 // We add the substring (except the end line)
929 if (i > 0)
930 lines_array.push_back(temp_text.substr(0, i));
931 else // It's only a new line
932 lines_array.push_back(ustring());
933
934 // We reached the string's end
935 if (i + 1 == text_length) {
936 temp_text.clear();
937 break;
938 }
939
940 // We then cut the temp string used part (and the new line)
941 temp_text = temp_text.substr(i + 1);
942 text_length = temp_text.length();
943 i = -1; // Will be set to 0 after the loop end.
944 }
945
946 // If there is still some text, we push the rest in the vector
947 if (temp_text.length() > 0)
948 lines_array.push_back(temp_text);
949
950 // We then perform word wrapping in a loop until all the text is added
951 // And copy it into the new vector
952 std::vector<vt_utils::ustring> wrapped_lines_array;
953 uint32_t num_lines = lines_array.size();
954 for (uint32_t line_index = 0; line_index < num_lines; ++line_index) {
955 ustring temp_line = lines_array[line_index];
956
957 // If it's an empty string, we add a blank line.
958 if (temp_line.empty()) {
959 wrapped_lines_array.push_back(temp_line);
960 continue;
961 }
962
963 // Some languages have spaces in the sentence, some don't (Japanese, Chinese, ...)
964 std::string locale = vt_system::SystemManager->GetLanguageLocale();
965 bool interwords_spaces = vt_system::SystemManager->GetLocaleProperty(locale).UsesInterWordsSpaces();
966
967 while(!temp_line.empty()) {
968 int32_t text_width = TextManager->CalculateTextWidth(ttf_font, temp_line);
969
970 // If the text can fit in the text box, add the whole line and return
971 if(text_width < (int32_t)max_width) {
972 wrapped_lines_array.push_back(temp_line);
973 break;
974 }
975
976 // Otherwise, find the maximum number of words which can fit and make that substring a line
977 // Word boundaries are found by calling the == 0x20 test
978 ustring wrapped_line;
979 int32_t num_wrapped_chars = 0;
980 int32_t last_breakable_index = -1;
981 int32_t line_length = static_cast<int32_t>(temp_line.length());
982 while (num_wrapped_chars < line_length) {
983 wrapped_line += temp_line[num_wrapped_chars];
984 // If we meet a space character (0x20), we can wrap the text
985 // If the current language don't have any spaces in the sentence, check all words.
986 if (!interwords_spaces || temp_line[num_wrapped_chars] == SPACE_CHAR) {
987 int32_t text_width = TextManager->CalculateTextWidth(ttf_font, wrapped_line);
988
989 if(text_width < (int32_t)max_width) {
990 // We haven't gone past the breaking point: mark this as a possible breaking point
991 last_breakable_index = num_wrapped_chars;
992 } else {
993 // We exceeded the maximum width, so go back to the previous breaking point.
994 // If there was no previous breaking point (== -1), then just break it off at
995 // the current character position.
996 if(last_breakable_index != -1)
997 num_wrapped_chars = last_breakable_index;
998 break;
999 }
1000 } // (temp_line[num_wrapped_chars] == SPACE_CHAR)
1001 ++num_wrapped_chars;
1002 } // while (num_wrapped_chars < line_length)
1003
1004 // Figure out the number of characters in the wrapped line and construct the wrapped line
1005 text_width = TextManager->CalculateTextWidth(ttf_font, wrapped_line);
1006 if(text_width >= (int32_t)max_width && last_breakable_index != -1) {
1007 num_wrapped_chars = last_breakable_index;
1008 }
1009 wrapped_line = temp_line.substr(0, num_wrapped_chars);
1010
1011 // Add the new wrapped line to the text.
1012 wrapped_lines_array.push_back(wrapped_line);
1013
1014 // If the current language has spaces in the sentence, the wrapped chars include a last space.
1015 if (interwords_spaces)
1016 num_wrapped_chars++;
1017 // If there is no more text remaining, we have finished.
1018 if (num_wrapped_chars >= line_length)
1019 break;
1020 // Otherwise, we need to grab the rest of the text that remains to be added and loop again.
1021 else
1022 temp_line = temp_line.substr(num_wrapped_chars, line_length - num_wrapped_chars);
1023 } // while (temp_line.empty() == false)
1024 } // for each lines of text
1025
1026 // Returns the wrapped lines.
1027 return wrapped_lines_array;
1028 }
1029
_RenderText(const uint16_t * text,FontProperties * font_properties,const Color & color)1030 void TextSupervisor::_RenderText(const uint16_t* text, FontProperties* font_properties, const Color& color)
1031 {
1032 if (text == nullptr || *text == 0) {
1033 IF_PRINT_WARNING(VIDEO_DEBUG) << "invalid argument, empty or null string" << std::endl;
1034 assert(text != nullptr && *text != 0);
1035 return;
1036 }
1037
1038 if (font_properties == nullptr || font_properties->ttf_font == nullptr) {
1039 IF_PRINT_WARNING(VIDEO_DEBUG) << "invalid argument, nullptr font properties or nullptr ttf font" << std::endl;
1040 assert(font_properties != nullptr && font_properties->ttf_font != nullptr);
1041 return;
1042 }
1043
1044 // Render the text.
1045 const SDL_Color white = { 255, 255, 255, 255 };
1046 SDL_Surface* surface = TTF_RenderUNICODE_Blended(font_properties->ttf_font, text, white);
1047 if (surface == nullptr) {
1048 IF_PRINT_WARNING(VIDEO_DEBUG) << "call to TTF_RenderUNICODE_Blended() failed" << std::endl;
1049 assert(surface != nullptr);
1050 return;
1051 }
1052
1053 // Retrieve the size of the text.
1054 int32_t font_width = 0, font_height = 0;
1055 if (TTF_SizeUNICODE(font_properties->ttf_font, text, &font_width, &font_height) != 0) {
1056 IF_PRINT_WARNING(VIDEO_DEBUG) << "call to TTF_SizeUNICODE() failed" << std::endl;
1057 SDL_FreeSurface(surface);
1058 assert(false);
1059 return;
1060 }
1061
1062 // Enable texturing.
1063 VideoManager->EnableTexture2D();
1064
1065 // Bind the OpenGL texture.
1066 TextureManager->_BindTexture(_text_texture);
1067
1068 // Lock the SDL surface.
1069 SDL_LockSurface(surface);
1070
1071 //
1072 // Send the surface pixel data to OpenGL.
1073 //
1074
1075 if (_text_texture_width == static_cast<GLuint>(surface->w) &&
1076 _text_texture_height == static_cast<GLuint>(surface->h)) {
1077 // The size of the old texture is the same. Just update the pixel data.
1078 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _text_texture_width, _text_texture_height, GL_RGBA, GL_UNSIGNED_BYTE, surface->pixels);
1079 } else {
1080 // The size of the old texture is different. Update the storage definition as well as the pixel data.
1081 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surface->w, surface->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surface->pixels);
1082 }
1083
1084 // Update the texture's width and height.
1085 _text_texture_width = surface->w;
1086 _text_texture_height = surface->h;
1087
1088 // Unlock the SDL surface.
1089 SDL_UnlockSurface(surface);
1090
1091 // Update some of the OpenGL texture parameters.
1092 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1093 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1094
1095 // Enable blending.
1096 VideoManager->EnableBlending();
1097
1098 // Update the blending function.
1099 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1100
1101 // Push the matrix stack.
1102 VideoManager->PushMatrix();
1103
1104 // Update the transmation matrix.
1105 CoordSys& coordinate_system = VideoManager->_current_context.coordinate_system;
1106 float x_offset = ((VideoManager->_current_context.x_align + 1) * font_width) * 0.5f * -coordinate_system.GetHorizontalDirection();
1107 float y_offset = ((VideoManager->_current_context.y_align + 1) * font_height) * 0.5f * -coordinate_system.GetVerticalDirection();
1108 VideoManager->MoveRelative(x_offset, y_offset);
1109
1110 // Load the shader program.
1111 gl::ShaderProgram* shader_program = VideoManager->LoadShaderProgram(gl::shader_programs::Sprite);
1112 assert(shader_program != nullptr);
1113
1114 // The vertex positions.
1115 float vertex_positions[] =
1116 {
1117 0.0f, 0.0f, 0.0f, // Vertex One.
1118 static_cast<float>(font_width), 0.0f, 0.0f, // Vertex Two.
1119 static_cast<float>(font_width), static_cast<float>(font_height), 0.0f, // Vertex Three.
1120 0.0f, static_cast<float>(font_height), 0.0f // Vertex Four.
1121 };
1122
1123 // The vertex texture coordinates.
1124 float vertex_texture_coordinates[] =
1125 {
1126 0.0f, 0.0f, // Vertex One.
1127 1.0f, 0.0f, // Vertex Two.
1128 1.0f, 1.0f, // Vertex Three.
1129 0.0f, 1.0f // Vertex Four.
1130 };
1131
1132 // The vertex colors.
1133 float vertex_colors[] =
1134 {
1135 1.0f, 1.0f, 1.0f, 1.0f, // Vertex One.
1136 1.0f, 1.0f, 1.0f, 1.0f, // Vertex Two.
1137 1.0f, 1.0f, 1.0f, 1.0f, // Vertex Three.
1138 1.0f, 1.0f, 1.0f, 1.0f // Vertex Four.
1139 };
1140
1141 // Draw the text.
1142 VideoManager->DrawSprite(shader_program, vertex_positions, vertex_texture_coordinates, vertex_colors, color);
1143
1144 // Unload the shader program.
1145 VideoManager->UnloadShaderProgram();
1146
1147 // Restore the transformation stack.
1148 VideoManager->PopMatrix();
1149
1150 // Clean up.
1151 if (surface != nullptr) {
1152 SDL_FreeSurface(surface);
1153 surface = nullptr;
1154 }
1155 }
1156
_RenderText(const uint16_t * text,FontProperties * font_properties,const Color & color,float shadow_offset_x,float shadow_offset_y,const Color & color_shadow)1157 void TextSupervisor::_RenderText(const uint16_t* text, FontProperties* font_properties,
1158 const Color& color,
1159 float shadow_offset_x, float shadow_offset_y,
1160 const Color& color_shadow)
1161 {
1162 if (text == nullptr || *text == 0) {
1163 IF_PRINT_WARNING(VIDEO_DEBUG) << "invalid argument, empty or null string" << std::endl;
1164 assert(text != nullptr && *text != 0);
1165 return;
1166 }
1167
1168 if (font_properties == nullptr || font_properties->ttf_font == nullptr) {
1169 IF_PRINT_WARNING(VIDEO_DEBUG) << "invalid argument, nullptr font properties or nullptr ttf font" << std::endl;
1170 assert(font_properties != nullptr && font_properties->ttf_font != nullptr);
1171 return;
1172 }
1173
1174 // Render the text.
1175 const SDL_Color white = { 255, 255, 255, 255 };
1176 SDL_Surface* surface = TTF_RenderUNICODE_Blended(font_properties->ttf_font, text, white);
1177 if (surface == nullptr) {
1178 IF_PRINT_WARNING(VIDEO_DEBUG) << "call to TTF_RenderUNICODE_Blended() failed" << std::endl;
1179 assert(surface != nullptr);
1180 return;
1181 }
1182
1183 // Retrieve the size of the text.
1184 int32_t font_width = 0, font_height = 0;
1185 if (TTF_SizeUNICODE(font_properties->ttf_font, text, &font_width, &font_height) != 0) {
1186 IF_PRINT_WARNING(VIDEO_DEBUG) << "call to TTF_SizeUNICODE() failed" << std::endl;
1187 SDL_FreeSurface(surface);
1188 assert(false);
1189 return;
1190 }
1191
1192 // Enable texturing.
1193 VideoManager->EnableTexture2D();
1194
1195 // Bind the OpenGL texture.
1196 TextureManager->_BindTexture(_text_texture);
1197
1198 // Lock the SDL surface.
1199 SDL_LockSurface(surface);
1200
1201 //
1202 // Send the surface pixel data to OpenGL.
1203 //
1204
1205 if (_text_texture_width == static_cast<GLuint>(surface->w) &&
1206 _text_texture_height == static_cast<GLuint>(surface->h)) {
1207 // The size of the old texture is the same. Just update the pixel data.
1208 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _text_texture_width, _text_texture_height, GL_RGBA, GL_UNSIGNED_BYTE, surface->pixels);
1209 } else {
1210 // The size of the old texture is different. Update the storage definition as well as the pixel data.
1211 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surface->w, surface->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surface->pixels);
1212 }
1213
1214 // Update the texture's width and height.
1215 _text_texture_width = surface->w;
1216 _text_texture_height = surface->h;
1217
1218 // Unlock the SDL surface.
1219 SDL_UnlockSurface(surface);
1220
1221 // Update some of the OpenGL texture parameters.
1222 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1223 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1224
1225 // Enable blending.
1226 VideoManager->EnableBlending();
1227
1228 // Update the blending function.
1229 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1230
1231 //
1232 // Draw the shadow first.
1233 //
1234
1235 // Push the transformation stack.
1236 VideoManager->PushMatrix();
1237
1238 // Apply the shadow offset.
1239 const float delta_x = VideoManager->_current_context.coordinate_system.GetHorizontalDirection() * shadow_offset_x;
1240 const float delta_y = VideoManager->_current_context.coordinate_system.GetVerticalDirection() * shadow_offset_y;
1241 VideoManager->MoveRelative(delta_x, delta_y);
1242
1243 // Update the transmation matrix.
1244 CoordSys& coordinate_system = VideoManager->_current_context.coordinate_system;
1245 float x_offset = ((VideoManager->_current_context.x_align + 1) * font_width) * 0.5f * -coordinate_system.GetHorizontalDirection();
1246 float y_offset = ((VideoManager->_current_context.y_align + 1) * font_height) * 0.5f * -coordinate_system.GetVerticalDirection();
1247 VideoManager->MoveRelative(x_offset, y_offset);
1248
1249 // Load the shader program.
1250 gl::ShaderProgram* shader_program = VideoManager->LoadShaderProgram(gl::shader_programs::Sprite);
1251 assert(shader_program != nullptr);
1252
1253 // The vertex positions.
1254 float vertex_positions[] =
1255 {
1256 0.0f, 0.0f, 0.0f, // Vertex One.
1257 static_cast<float>(font_width), 0.0f, 0.0f, // Vertex Two.
1258 static_cast<float>(font_width), static_cast<float>(font_height), 0.0f, // Vertex Three.
1259 0.0f, static_cast<float>(font_height), 0.0f // Vertex Four.
1260 };
1261
1262 // The vertex texture coordinates.
1263 float vertex_texture_coordinates[] =
1264 {
1265 0.0f, 0.0f, // Vertex One.
1266 1.0f, 0.0f, // Vertex Two.
1267 1.0f, 1.0f, // Vertex Three.
1268 0.0f, 1.0f // Vertex Four.
1269 };
1270
1271 // The vertex colors.
1272 float vertex_colors[] =
1273 {
1274 1.0f, 1.0f, 1.0f, 1.0f, // Vertex One.
1275 1.0f, 1.0f, 1.0f, 1.0f, // Vertex Two.
1276 1.0f, 1.0f, 1.0f, 1.0f, // Vertex Three.
1277 1.0f, 1.0f, 1.0f, 1.0f // Vertex Four.
1278 };
1279
1280 // Draw the shadow.
1281 VideoManager->DrawSprite(shader_program, vertex_positions, vertex_texture_coordinates, vertex_colors, color_shadow);
1282
1283 // Restore the transformation stack.
1284 VideoManager->PopMatrix();
1285
1286 //
1287 // Draw the text second.
1288 //
1289
1290 // Push the transformation stack.
1291 VideoManager->PushMatrix();
1292
1293 // Update the transmation matrix.
1294 VideoManager->MoveRelative(x_offset, y_offset);
1295
1296 // Draw the text.
1297 VideoManager->DrawSprite(shader_program, vertex_positions, vertex_texture_coordinates, vertex_colors, color);
1298
1299 // Unload the shader program.
1300 VideoManager->UnloadShaderProgram();
1301
1302 // Restore the transformation stack.
1303 VideoManager->PopMatrix();
1304
1305 // Clean up.
1306 if (surface != nullptr) {
1307 SDL_FreeSurface(surface);
1308 surface = nullptr;
1309 }
1310 }
1311
_RenderText(const vt_utils::ustring & text,TextStyle & style,ImageMemory & buffer)1312 bool TextSupervisor::_RenderText(const vt_utils::ustring& text, TextStyle& style, ImageMemory& buffer)
1313 {
1314 FontProperties* font_properties = style.GetFontProperties();
1315 if (font_properties == nullptr || font_properties->ttf_font == nullptr) {
1316 IF_PRINT_WARNING(VIDEO_DEBUG) << "The TextStyle argument using font:'" << style.GetFontName() << "' was invalid" << std::endl;
1317 assert(font_properties != nullptr && font_properties->ttf_font == nullptr);
1318 return false;
1319 }
1320
1321 // Render the text.
1322 const SDL_Color white = { 255, 255, 255, 255 };
1323 SDL_Surface* surface = TTF_RenderUNICODE_Blended(font_properties->ttf_font, text.c_str(), white);
1324 if (surface == nullptr) {
1325 IF_PRINT_WARNING(VIDEO_DEBUG) << "call to TTF_RenderUNICODE_Blended() failed" << std::endl;
1326 assert(surface != nullptr);
1327 return false;
1328 }
1329
1330 // Copy the text to the buffer.
1331 buffer = ImageMemory(surface);
1332
1333 // Clean up.
1334 SDL_FreeSurface(surface);
1335 surface = nullptr;
1336
1337 return true;
1338 }
1339
1340 } // namespace vt_video
1341