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 &copy) :
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 &copy)
429 {
430     // Prevents object assignment to itself
431     if(this == &copy)
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