Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
GUItext.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2013 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 /*
19 GUI text
20 */
21 
22 #include "precompiled.h"
23 
24 #include "GUI.h"
25 #include "GUIManager.h"
26 #include "ps/CLogger.h"
27 #include "ps/Parser.h"
28 #include <algorithm>
29 
30 #include "ps/Font.h"
31 
32 
33 static const wchar_t TagStart = '[';
34 static const wchar_t TagEnd = ']';
35 
37 {
38  m_Images[Left].clear();
39  m_Images[Right].clear();
40  m_TextCalls.clear();
41  m_SpriteCalls.clear();
42  m_Size = CSize();
43  m_NewLine=false;
44 }
45 
47  const CStrW& DefaultFont,
48  const int &from, const int &to,
49  const bool FirstLine,
50  const IGUIObject *pObject) const
51 {
52  // Reset width and height, because they will be determined with incrementation
53  // or comparisons.
54  Feedback.Reset();
55 
56  // Check out which text chunk this is within.
57  //bool match_found = false;
58  std::vector<TextChunk>::const_iterator itTextChunk;
59  for (itTextChunk=m_TextChunks.begin(); itTextChunk!=m_TextChunks.end(); ++itTextChunk)
60  {
61  // Get the area that is overlapped by both the TextChunk and
62  // by the from/to inputted.
63  int _from, _to;
64  _from = std::max(from, itTextChunk->m_From);
65  _to = std::min(to, itTextChunk->m_To);
66 
67  // If from is larger than to, than they are not overlapping
68  if (_to == _from && itTextChunk->m_From == itTextChunk->m_To)
69  {
70  // These should never be able to have more than one tag.
71  ENSURE(itTextChunk->m_Tags.size()==1);
72 
73  // Now do second check
74  // because icons and images are placed on exactly one position
75  // in the words-list, it can be counted twice if placed on an
76  // edge. But there is always only one logical preference that
77  // we want. This check filters the unwanted.
78 
79  // it's in the end of one word, and the icon
80  // should really belong to the beginning of the next one
81  if (_to == to && to >= 1)
82  {
83  if (GetRawString()[to-1] == ' ' ||
84  GetRawString()[to-1] == '-' ||
85  GetRawString()[to-1] == '\n')
86  continue;
87  }
88  // This std::string is just a break
89  if (_from == from && from >= 1)
90  {
91  if (GetRawString()[from] == '\n' &&
92  GetRawString()[from-1] != '\n' &&
93  GetRawString()[from-1] != ' ' &&
94  GetRawString()[from-1] != '-')
95  continue;
96  }
97 
98  // Single tags
99  if (itTextChunk->m_Tags[0].m_TagType == CGUIString::TextChunk::Tag::TAG_IMGLEFT)
100  {
101  // Only add the image if the icon exists.
102  if (g_GUI->IconExists(itTextChunk->m_Tags[0].m_TagValue))
103  {
104  Feedback.m_Images[SFeedback::Left].push_back(itTextChunk->m_Tags[0].m_TagValue);
105  }
106  else if (pObject)
107  {
108  LOGERROR(L"Trying to use an [imgleft]-tag with an undefined icon (\"%hs\").", itTextChunk->m_Tags[0].m_TagValue.c_str());
109  }
110  }
111  else
112  if (itTextChunk->m_Tags[0].m_TagType == CGUIString::TextChunk::Tag::TAG_IMGRIGHT)
113  {
114  // Only add the image if the icon exists.
115  if (g_GUI->IconExists(itTextChunk->m_Tags[0].m_TagValue))
116  {
117  Feedback.m_Images[SFeedback::Right].push_back(itTextChunk->m_Tags[0].m_TagValue);
118  }
119  else if (pObject)
120  {
121  LOGERROR(L"Trying to use an [imgright]-tag with an undefined icon (\"%hs\").", itTextChunk->m_Tags[0].m_TagValue.c_str());
122  }
123  }
124  else
125  if (itTextChunk->m_Tags[0].m_TagType == CGUIString::TextChunk::Tag::TAG_ICON)
126  {
127  // Only add the image if the icon exists.
128  if (g_GUI->IconExists(itTextChunk->m_Tags[0].m_TagValue))
129  {
130  // We'll need to setup a text-call that will point
131  // to the icon, this is to be able to iterate
132  // through the text-calls without having to
133  // complex the structure virtually for nothing more.
134  SGUIText::STextCall TextCall;
135 
136  // Also add it to the sprites being rendered.
137  SGUIText::SSpriteCall SpriteCall;
138 
139  // Get Icon from icon database in g_GUI
140  SGUIIcon icon = g_GUI->GetIcon(itTextChunk->m_Tags[0].m_TagValue);
141 
142  CSize size = icon.m_Size;
143 
144  // append width, and make maximum height the height.
145  Feedback.m_Size.cx += size.cx;
146  Feedback.m_Size.cy = std::max(Feedback.m_Size.cy, size.cy);
147 
148  // These are also needed later
149  TextCall.m_Size = size;
150  SpriteCall.m_Area = size;
151 
152  // Handle additional attributes
153  std::vector<TextChunk::Tag::TagAttribute>::const_iterator att_it;
154  for(att_it = itTextChunk->m_Tags[0].m_TagAttributes.begin(); att_it != itTextChunk->m_Tags[0].m_TagAttributes.end(); ++att_it)
155  {
156  TextChunk::Tag::TagAttribute tagAttrib = (TextChunk::Tag::TagAttribute)(*att_it);
157 
158  if (tagAttrib.attrib == "displace" && !tagAttrib.value.empty())
159  { //Displace the sprite
160  CSize displacement;
161  // Parse the value
162  if (!GUI<CSize>::ParseString(CStr(tagAttrib.value).FromUTF8(), displacement))
163  LOGERROR(L"Error parsing 'displace' value for tag [ICON]");
164  else
165  SpriteCall.m_Area += displacement;
166 
167  }
168  else if(tagAttrib.attrib == "tooltip")
169  {
170  SpriteCall.m_Tooltip = CStr(tagAttrib.value).FromUTF8();
171  }
172  else if(tagAttrib.attrib == "tooltip_style")
173  {
174  SpriteCall.m_TooltipStyle = CStr(tagAttrib.value).FromUTF8();
175  }
176  }
177 
178  SpriteCall.m_Sprite = icon.m_SpriteName;
179  SpriteCall.m_CellID = icon.m_CellID;
180 
181  // Add sprite call
182  Feedback.m_SpriteCalls.push_back(SpriteCall);
183 
184  // Finalize text call
185  TextCall.m_pSpriteCall = &Feedback.m_SpriteCalls.back();
186 
187  // Add text call
188  Feedback.m_TextCalls.push_back(TextCall);
189  }
190  else if (pObject)
191  {
192  LOGERROR(L"Trying to use an [icon]-tag with an undefined icon (\"%hs\").", itTextChunk->m_Tags[0].m_TagValue.c_str());
193  }
194  }
195  }
196  else
197  if (_to > _from && !Feedback.m_NewLine)
198  {
199  SGUIText::STextCall TextCall;
200 
201  // Set defaults
202  TextCall.m_Font = DefaultFont;
203  TextCall.m_UseCustomColor = false;
204 
205  // Extract substd::string from RawString.
206  TextCall.m_String = GetRawString().substr(_from, _to-_from);
207 
208  // Go through tags and apply changes.
209  std::vector<CGUIString::TextChunk::Tag>::const_iterator it2;
210  for (it2 = itTextChunk->m_Tags.begin(); it2 != itTextChunk->m_Tags.end(); ++it2)
211  {
212  if (it2->m_TagType == CGUIString::TextChunk::Tag::TAG_COLOR)
213  {
214  // Set custom color
215  TextCall.m_UseCustomColor = true;
216 
217  // Try parsing the color std::string
218  if (!GUI<CColor>::ParseString(CStr(it2->m_TagValue).FromUTF8(), TextCall.m_Color))
219  {
220  if (pObject)
221  LOGERROR(L"Error parsing the value of a [color]-tag in GUI text when reading object \"%hs\".", pObject->GetPresentableName().c_str());
222  }
223  }
224  else
225  if (it2->m_TagType == CGUIString::TextChunk::Tag::TAG_FONT)
226  {
227  // TODO Gee: (2004-08-15) Check if Font exists?
228  TextCall.m_Font = CStr(it2->m_TagValue).FromUTF8();
229  }
230  }
231 
232  // Calculate the size of the font
233  CSize size;
234  int cx, cy;
235  CFont font (TextCall.m_Font);
236  font.CalculateStringSize(TextCall.m_String.c_str(), cx, cy);
237  // For anything other than the first line, the line spacing
238  // needs to be considered rather than just the height of the text
239  if (! FirstLine)
240  cy = font.GetLineSpacing();
241 
242  size.cx = (float)cx;
243  size.cy = (float)cy;
244 
245  // Append width, and make maximum height the height.
246  Feedback.m_Size.cx += size.cx;
247  Feedback.m_Size.cy = std::max(Feedback.m_Size.cy, size.cy);
248 
249  // These are also needed later
250  TextCall.m_Size = size;
251 
252  if (! TextCall.m_String.empty())
253  {
254  if (TextCall.m_String[0] == '\n')
255  {
256  Feedback.m_NewLine = true;
257  }
258  }
259 
260  // Add text-chunk
261  Feedback.m_TextCalls.push_back(TextCall);
262  }
263  }
264 }
265 
266 bool CGUIString::TextChunk::Tag::SetTagType(const CStr& tagtype)
267 {
268  CStr _tagtype = tagtype.UpperCase();
269 
270  if (_tagtype == CStr("COLOR"))
271  {
272  m_TagType = TAG_COLOR;
273  return true;
274  }
275  else
276  if (_tagtype == CStr("FONT"))
277  {
278  m_TagType = TAG_FONT;
279  return true;
280  }
281  else
282  if (_tagtype == CStr("ICON"))
283  {
284  m_TagType = TAG_ICON;
285  return true;
286  }
287  else
288  if (_tagtype == CStr("IMGLEFT"))
289  {
290  m_TagType = TAG_IMGLEFT;
291  return true;
292  }
293  else
294  if (_tagtype == CStr("IMGRIGHT"))
295  {
296  m_TagType = TAG_IMGRIGHT;
297  return true;
298  }
299 
300  return false;
301 }
302 
303 void CGUIString::SetValue(const CStrW& str)
304 {
305  m_OriginalString = str;
306 
307  // clear
308  m_TextChunks.clear();
309  m_Words.clear();
310  m_RawString = CStrW();
311 
312  // Setup parser
313  // TODO Gee: (2004-08-16) Create and store this parser object somewhere to save loading time.
314  // TODO PT: Extended CParserCache so that the above is possible (since it currently only
315  // likes one-task parsers)
316  CParser Parser;
317  // I've added the option of an additional parameter. Only used for icons when writing this.
318  Parser.InputTaskType("start", "$ident[_=_$value_[$ident_=_$value_]]");
319  Parser.InputTaskType("end", "/$ident");
320 
321  long position = 0;
322  long from=0; // the position in the raw std::string where the last tag ended
323  long from_nonraw=0; // like from only in position of the REAL std::string, with tags.
324  long curpos = 0;
325 
326  // Current Text Chunk
327  CGUIString::TextChunk CurrentTextChunk;
328 
329  for (;;position = curpos+1)
330  {
331  // Find next TagStart character
332  curpos = str.Find(position, TagStart);
333 
334  if (curpos == -1)
335  {
336  m_RawString += str.substr(position);
337 
338  if (from != (long)m_RawString.length())
339  {
340  CurrentTextChunk.m_From = from;
341  CurrentTextChunk.m_To = (int)m_RawString.length();
342  m_TextChunks.push_back(CurrentTextChunk);
343  }
344 
345  break;
346  }
347  else
348  {
349  // First check if there is another TagStart before a TagEnd,
350  // in that case it's just a regular TagStart and we can continue.
351  long pos_left = str.Find(curpos+1, TagStart);
352  long pos_right = str.Find(curpos+1, TagEnd);
353 
354  if (pos_right == -1)
355  {
356  m_RawString += str.substr(position, curpos-position+1);
357  continue;
358  }
359  else
360  if (pos_left != -1 && pos_left < pos_right)
361  {
362  m_RawString += str.substr(position, pos_left-position);
363  continue;
364  }
365  else
366  {
367  m_RawString += str.substr(position, curpos-position);
368 
369  // Okay we've found a TagStart and TagEnd, positioned
370  // at pos and pos_right. Now let's extract the
371  // interior and try parsing.
372  CStrW tagstr (str.substr(curpos+1, pos_right-curpos-1));
373 
374  CParserLine Line;
375  Line.ParseString(Parser, tagstr.ToUTF8());
376 
377  // Set to true if the tag is just text.
378  bool justtext = false;
379 
380  if (Line.m_ParseOK)
381  {
382  if (Line.m_TaskTypeName == "start")
383  {
384  // The tag
385  TextChunk::Tag tag;
386  std::string Str_TagType;
387 
388  Line.GetArgString(0, Str_TagType);
389 
390  if (!tag.SetTagType(Str_TagType))
391  {
392  justtext = true;
393  }
394  else
395  {
396  // Check for possible value-std::strings
397  if (Line.GetArgCount() >= 2)
398  Line.GetArgString(1, tag.m_TagValue);
399 
400  //Handle arbitrary number of additional parameters
401  size_t argn;
402  for(argn = 2; argn < Line.GetArgCount(); argn += 2)
403  {
404  TextChunk::Tag::TagAttribute a;
405 
406  Line.GetArgString(argn, a.attrib);
407  Line.GetArgString(argn+1, a.value);
408 
409  tag.m_TagAttributes.push_back(a);
410  }
411 
412  // Finalize last
413  if (curpos != from_nonraw)
414  {
415  CurrentTextChunk.m_From = from;
416  CurrentTextChunk.m_To = from + curpos - from_nonraw;
417  m_TextChunks.push_back(CurrentTextChunk);
418  from = CurrentTextChunk.m_To;
419  }
420  from_nonraw = pos_right+1;
421 
422  // Some tags does not have a closure, and should be
423  // stored without text. Like a <tag /> in XML.
427  {
428  // We need to use a fresh text chunk
429  // because 'tag' should be the *only* tag.
430  TextChunk FreshTextChunk;
431 
432  // They does not work with the text system.
433  FreshTextChunk.m_From = from + pos_right+1 - from_nonraw;
434  FreshTextChunk.m_To = from + pos_right+1 - from_nonraw;
435 
436  FreshTextChunk.m_Tags.push_back(tag);
437 
438  m_TextChunks.push_back(FreshTextChunk);
439  }
440  else
441  {
442  // Add that tag, but first, erase previous occurences of the
443  // same tag.
444  std::vector<TextChunk::Tag>::iterator it;
445  for (it = CurrentTextChunk.m_Tags.begin(); it != CurrentTextChunk.m_Tags.end(); ++it)
446  {
447  if (it->m_TagType == tag.m_TagType)
448  {
449  CurrentTextChunk.m_Tags.erase(it);
450  break;
451  }
452  }
453 
454  // Add!
455  CurrentTextChunk.m_Tags.push_back(tag);
456  }
457  }
458  }
459  else
460  if (Line.m_TaskTypeName == "end")
461  {
462  // The tag
463  TextChunk::Tag tag;
464  std::string Str_TagType;
465 
466  Line.GetArgString(0, Str_TagType);
467 
468  if (!tag.SetTagType(Str_TagType))
469  {
470  justtext = true;
471  }
472  else
473  {
474  // Finalize the previous chunk
475  if (curpos != from_nonraw)
476  {
477  CurrentTextChunk.m_From = from;
478  CurrentTextChunk.m_To = from + curpos - from_nonraw;
479  m_TextChunks.push_back(CurrentTextChunk);
480  from = CurrentTextChunk.m_To;
481  }
482  from_nonraw = pos_right+1;
483 
484  // Search for the tag, if it's not added, then
485  // pass it as plain text.
486  std::vector<TextChunk::Tag>::iterator it;
487  for (it = CurrentTextChunk.m_Tags.begin(); it != CurrentTextChunk.m_Tags.end(); ++it)
488  {
489  if (it->m_TagType == tag.m_TagType)
490  {
491  CurrentTextChunk.m_Tags.erase(it);
492  break;
493  }
494  }
495  }
496  }
497  }
498  else justtext = true;
499 
500  if (justtext)
501  {
502  // What was within the tags could not be interpreted
503  // so we'll assume it's just text.
504  m_RawString += str.substr(curpos, pos_right-curpos+1);
505  }
506 
507  curpos = pos_right;
508 
509  continue;
510  }
511  }
512  }
513 
514  // Add a delimiter at start and at end, it helps when
515  // processing later, because we don't have make exceptions for
516  // those cases.
517  // We'll sort later.
518  m_Words.push_back(0);
519  m_Words.push_back((int)m_RawString.length());
520 
521  // Space: ' '
522  for (position=0, curpos=0;;position = curpos+1)
523  {
524  // Find the next word-delimiter.
525  long dl = m_RawString.Find(position, ' ');
526 
527  if (dl == -1)
528  break;
529 
530  curpos = dl;
531  m_Words.push_back((int)dl+1);
532  }
533 
534  // Dash: '-'
535  for (position=0, curpos=0;;position = curpos+1)
536  {
537  // Find the next word-delimiter.
538  long dl = m_RawString.Find(position, '-');
539 
540  if (dl == -1)
541  break;
542 
543  curpos = dl;
544  m_Words.push_back((int)dl+1);
545  }
546 
547  // New Line: '\n'
548  for (position=0, curpos=0;;position = curpos+1)
549  {
550  // Find the next word-delimiter.
551  long dl = m_RawString.Find(position, '\n');
552 
553  if (dl == -1)
554  break;
555 
556  curpos = dl;
557 
558  // Add before and
559  m_Words.push_back((int)dl);
560  m_Words.push_back((int)dl+1);
561  }
562 
563  sort(m_Words.begin(), m_Words.end());
564 
565  // Remove duplicates (only if larger than 2)
566  if (m_Words.size() > 2)
567  {
568  std::vector<int>::iterator it;
569  int last_word = -1;
570  for (it = m_Words.begin(); it != m_Words.end(); )
571  {
572  if (last_word == *it)
573  {
574  it = m_Words.erase(it);
575  }
576  else
577  {
578  last_word = *it;
579  ++it;
580  }
581  }
582  }
583 
584 #if 0
585  for (int i=0; i<(int)m_Words.size(); ++i)
586  {
587  LOGMESSAGE(L"m_Words[%d] = %d", i, m_Words[i]);
588  }
589 
590  for (int i=0; i<(int)m_TextChunks.size(); ++i)
591  {
592  LOGMESSAGE(L"m_TextChunk[%d] = [%d,%d]", i, m_TextChunks[i].m_From, m_TextChunks[i].m_To);
593  for (int j=0; j<(int)m_TextChunks[i].m_Tags.size(); ++j)
594  {
595  LOGMESSAGE(L"--Tag: %d \"%hs\"", (int)m_TextChunks[i].m_Tags[j].m_TagType, m_TextChunks[i].m_Tags[j].m_TagValue.c_str());
596  }
597  }
598 #endif
599 }
int GetLineSpacing()
Definition: Font.cpp:55
A chunk of text that represents one call to the renderer.
Definition: GUItext.h:184
int m_CellID
Definition: GUIbase.h:170
Made to represent a screen size, should in philosophy be made of unsigned ints, but for the sake of c...
Definition: Overlay.h:205
void CalculateStringSize(const wchar_t *string, int &w, int &h)
Definition: Font.cpp:70
static const int Right
Definition: GUItext.h:256
#define LOGERROR
Definition: CLogger.h:35
std::list< SSpriteCall >::pointer m_pSpriteCall
IF an icon, then this is not NULL.
Definition: GUItext.h:143
All data generated in GenerateTextCall()
Definition: GUItext.h:252
std::vector< SGUIText::STextCall > m_TextCalls
Text and Sprite Calls.
Definition: GUItext.h:271
std::vector< Tag > m_Tags
Tags that are present.
Definition: GUItext.h:246
#define LOGMESSAGE
Definition: CLogger.h:32
bool SetTagType(const CStr &tagtype)
Set tag from string.
Definition: GUItext.cpp:266
CSize m_Size
Width and Height feedback
Definition: GUItext.h:278
CStrW m_TooltipStyle
Tooltip style.
Definition: GUItext.h:91
void GenerateTextCall(SFeedback &Feedback, const CStrW &DefaultFont, const int &from, const int &to, const bool FirstLine, const IGUIObject *pObject=NULL) const
Generate Text Call from specified range.
Definition: GUItext.cpp:46
CRect m_Area
Size and position of sprite.
Definition: GUItext.h:74
Base settings, all objects possess these settings in their m_BaseSettings Instructions can be found i...
Definition: IGUIObject.h:140
A sprite call to the CRenderer.
Definition: GUItext.h:67
CStr GetPresentableName() const
Definition: IGUIObject.cpp:536
CGUIManager * g_GUI
Definition: GUIManager.cpp:32
std::vector< TextChunk > m_TextChunks
TextChunks.
Definition: GUItext.h:334
bool IconExists(const CStr &str) const
See CGUI::IconExists; applies to the currently active page.
Definition: GUIManager.cpp:236
const CStrW & GetRawString() const
Get String, without tags.
Definition: GUItext.h:295
static const wchar_t TagEnd
Definition: GUItext.cpp:34
Includes static functions that needs one template argument.
Definition: GUIutil.h:103
SGUIIcon GetIcon(const CStr &str) const
See CGUI::GetIcon; applies to the currently active page.
Definition: GUIManager.cpp:241
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
int m_From
m_From and m_To is the range of the string
Definition: GUItext.h:241
Definition: Font.h:28
std::vector< CStr > m_Images[2]
Image stacks, for left and right floating images.
Definition: GUItext.h:266
bool m_UseCustomColor
Use custom color? If true then m_Color is used, else the color inputted will be used.
Definition: GUItext.h:123
CStr m_SpriteName
Definition: GUIbase.h:164
static const int Left
Definition: GUItext.h:255
void SetValue(const CStrW &str)
Set the value, the string will automatically be parsed when set.
Definition: GUItext.cpp:303
CStrW m_String
The string that is suppose to be rendered.
Definition: GUItext.h:117
CColor m_Color
Color setup.
Definition: GUItext.h:128
bool ParseString(const CParser &parser, const std::string &line)
Definition: Parser.cpp:325
CStrW m_Font
Font name.
Definition: GUItext.h:133
CStrW m_RawString
The full raw string.
Definition: GUItext.h:340
std::vector< int > m_Words
Words.
Definition: GUItext.h:329
bool InputTaskType(const std::string &strName, const std::string &strSyntax)
Definition: Parser.cpp:816
CStrW m_OriginalString
The original string value passed to SetValue.
Definition: GUItext.h:345
std::list< SGUIText::SSpriteCall > m_SpriteCalls
Definition: GUItext.h:272
TagType m_TagType
In [B=Hello][/B] m_TagType is TAG_B.
Definition: GUItext.h:224
bool m_NewLine
If the word inputted was a new line.
Definition: GUItext.h:283
A tag looks like this &quot;Hello [B]there[/B] little&quot;.
Definition: GUItext.h:189
float cy
Definition: Overlay.h:234
const wchar_t * DefaultFont
Definition: Font.cpp:29
CSize m_Size
Definition: GUIbase.h:167
void Reset()
Reset all member data.
Definition: GUItext.cpp:36
CSize m_Size
Size.
Definition: GUItext.h:112
A text call to the CRenderer.
Definition: GUItext.h:97
std::string m_TagValue
In [B=Hello][/B] m_TagValue is &#39;Hello&#39;.
Definition: GUItext.h:230
CGUISpriteInstance m_Sprite
Sprite from global GUI sprite database.
Definition: GUItext.h:79
CStrW m_Tooltip
Tooltip text.
Definition: GUItext.h:86
std::vector< TagAttribute > m_TagAttributes
Some tags need an additional attributes.
Definition: GUItext.h:235
float cx
Size.
Definition: Overlay.h:234
static const wchar_t TagStart
Definition: GUItext.cpp:33