1 //
2 // Copyright (c) 2008-2017 the Urho3D project.
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE.
21 //
22 
23 #include "../Precompiled.h"
24 
25 #include "../Core/Context.h"
26 #include "../Core/Profiler.h"
27 #include "../Graphics/Texture2D.h"
28 #include "../IO/Log.h"
29 #include "../Resource/ResourceCache.h"
30 #include "../UI/Font.h"
31 #include "../UI/FontFace.h"
32 #include "../UI/Text.h"
33 #include "../Resource/Localization.h"
34 #include "../Resource/ResourceEvents.h"
35 
36 #include "../DebugNew.h"
37 
38 namespace Urho3D
39 {
40 
41 const char* textEffects[] =
42 {
43     "None",
44     "Shadow",
45     "Stroke",
46     0
47 };
48 
49 static const float MIN_ROW_SPACING = 0.5f;
50 
51 extern const char* horizontalAlignments[];
52 extern const char* UI_CATEGORY;
53 
Text(Context * context)54 Text::Text(Context* context) :
55     UIElement(context),
56     fontSize_(DEFAULT_FONT_SIZE),
57     textAlignment_(HA_LEFT),
58     rowSpacing_(1.0f),
59     wordWrap_(false),
60     autoLocalizable_(false),
61     charLocationsDirty_(true),
62     selectionStart_(0),
63     selectionLength_(0),
64     selectionColor_(Color::TRANSPARENT),
65     hoverColor_(Color::TRANSPARENT),
66     textEffect_(TE_NONE),
67     shadowOffset_(IntVector2(1, 1)),
68     strokeThickness_(1),
69     roundStroke_(false),
70     effectColor_(Color::BLACK),
71     effectDepthBias_(0.0f),
72     rowHeight_(0)
73 {
74     // By default Text does not derive opacity from parent elements
75     useDerivedOpacity_ = false;
76 }
77 
~Text()78 Text::~Text()
79 {
80 }
81 
RegisterObject(Context * context)82 void Text::RegisterObject(Context* context)
83 {
84     context->RegisterFactory<Text>(UI_CATEGORY);
85 
86     URHO3D_COPY_BASE_ATTRIBUTES(UIElement);
87     URHO3D_UPDATE_ATTRIBUTE_DEFAULT_VALUE("Use Derived Opacity", false);
88     URHO3D_MIXED_ACCESSOR_ATTRIBUTE("Font", GetFontAttr, SetFontAttr, ResourceRef, ResourceRef(Font::GetTypeStatic()), AM_FILE);
89     URHO3D_ATTRIBUTE("Font Size", float, fontSize_, DEFAULT_FONT_SIZE, AM_FILE);
90     URHO3D_MIXED_ACCESSOR_ATTRIBUTE("Text", GetTextAttr, SetTextAttr, String, String::EMPTY, AM_FILE);
91     URHO3D_ENUM_ATTRIBUTE("Text Alignment", textAlignment_, horizontalAlignments, HA_LEFT, AM_FILE);
92     URHO3D_ATTRIBUTE("Row Spacing", float, rowSpacing_, 1.0f, AM_FILE);
93     URHO3D_ATTRIBUTE("Word Wrap", bool, wordWrap_, false, AM_FILE);
94     URHO3D_ACCESSOR_ATTRIBUTE("Auto Localizable", GetAutoLocalizable, SetAutoLocalizable, bool, false, AM_FILE);
95     URHO3D_ACCESSOR_ATTRIBUTE("Selection Color", GetSelectionColor, SetSelectionColor, Color, Color::TRANSPARENT, AM_FILE);
96     URHO3D_ACCESSOR_ATTRIBUTE("Hover Color", GetHoverColor, SetHoverColor, Color, Color::TRANSPARENT, AM_FILE);
97     URHO3D_ENUM_ATTRIBUTE("Text Effect", textEffect_, textEffects, TE_NONE, AM_FILE);
98     URHO3D_ATTRIBUTE("Shadow Offset", IntVector2, shadowOffset_, IntVector2(1, 1), AM_FILE);
99     URHO3D_ATTRIBUTE("Stroke Thickness", int, strokeThickness_, 1, AM_FILE);
100     URHO3D_ATTRIBUTE("Round Stroke", bool, roundStroke_, false, AM_FILE);
101     URHO3D_ACCESSOR_ATTRIBUTE("Effect Color", GetEffectColor, SetEffectColor, Color, Color::BLACK, AM_FILE);
102 
103     // Change the default value for UseDerivedOpacity
104     context->GetAttribute<Text>("Use Derived Opacity")->defaultValue_ = false;
105 }
106 
ApplyAttributes()107 void Text::ApplyAttributes()
108 {
109     UIElement::ApplyAttributes();
110 
111     // Localize now if attributes were loaded out-of-order
112     if (autoLocalizable_ && stringId_.Length())
113     {
114         Localization* l10n = GetSubsystem<Localization>();
115         text_ = l10n->Get(stringId_);
116     }
117 
118     DecodeToUnicode();
119 
120     fontSize_ = Max(fontSize_, 1);
121     strokeThickness_ = Abs(strokeThickness_);
122     ValidateSelection();
123     UpdateText();
124 }
125 
GetBatches(PODVector<UIBatch> & batches,PODVector<float> & vertexData,const IntRect & currentScissor)126 void Text::GetBatches(PODVector<UIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor)
127 {
128     FontFace* face = font_ ? font_->GetFace(fontSize_) : (FontFace*)0;
129     if (!face)
130     {
131         hovering_ = false;
132         return;
133     }
134 
135     // If face has changed or char locations are not valid anymore, update before rendering
136     if (charLocationsDirty_ || !fontFace_ || face != fontFace_)
137         UpdateCharLocations();
138     // If face uses mutable glyphs mechanism, reacquire glyphs before rendering to make sure they are in the texture
139     else if (face->HasMutableGlyphs())
140     {
141         for (unsigned i = 0; i < printText_.Size(); ++i)
142             face->GetGlyph(printText_[i]);
143     }
144 
145     // Hovering and/or whole selection batch
146     if ((hovering_ && hoverColor_.a_ > 0.0) || (selected_ && selectionColor_.a_ > 0.0f))
147     {
148         bool both = hovering_ && selected_ && hoverColor_.a_ > 0.0 && selectionColor_.a_ > 0.0f;
149         UIBatch batch(this, BLEND_ALPHA, currentScissor, 0, &vertexData);
150         batch.SetColor(both ? selectionColor_.Lerp(hoverColor_, 0.5f) :
151             (selected_ && selectionColor_.a_ > 0.0f ? selectionColor_ : hoverColor_));
152         batch.AddQuad(0, 0, GetWidth(), GetHeight(), 0, 0);
153         UIBatch::AddOrMerge(batch, batches);
154     }
155 
156     // Partial selection batch
157     if (!selected_ && selectionLength_ && charLocations_.Size() >= selectionStart_ + selectionLength_ && selectionColor_.a_ > 0.0f)
158     {
159         UIBatch batch(this, BLEND_ALPHA, currentScissor, 0, &vertexData);
160         batch.SetColor(selectionColor_);
161 
162         Vector2 currentStart = charLocations_[selectionStart_].position_;
163         Vector2 currentEnd = currentStart;
164         for (unsigned i = selectionStart_; i < selectionStart_ + selectionLength_; ++i)
165         {
166             // Check if row changes, and start a new quad in that case
167             if (charLocations_[i].size_ != Vector2::ZERO)
168             {
169                 if (charLocations_[i].position_.y_ != currentStart.y_)
170                 {
171                     batch.AddQuad(currentStart.x_, currentStart.y_, currentEnd.x_ - currentStart.x_,
172                         currentEnd.y_ - currentStart.y_, 0, 0);
173                     currentStart = charLocations_[i].position_;
174                     currentEnd = currentStart + charLocations_[i].size_;
175                 }
176                 else
177                 {
178                     currentEnd.x_ += charLocations_[i].size_.x_;
179                     currentEnd.y_ = Max(currentStart.y_ + charLocations_[i].size_.y_, currentEnd.y_);
180                 }
181             }
182         }
183         if (currentEnd != currentStart)
184         {
185             batch.AddQuad(currentStart.x_, currentStart.y_, currentEnd.x_ - currentStart.x_, currentEnd.y_ - currentStart.y_, 0, 0);
186         }
187 
188         UIBatch::AddOrMerge(batch, batches);
189     }
190 
191     // Text batch
192     TextEffect textEffect = font_->IsSDFFont() ? TE_NONE : textEffect_;
193     const Vector<SharedPtr<Texture2D> >& textures = face->GetTextures();
194     for (unsigned n = 0; n < textures.Size() && n < pageGlyphLocations_.Size(); ++n)
195     {
196         // One batch per texture/page
197         UIBatch pageBatch(this, BLEND_ALPHA, currentScissor, textures[n], &vertexData);
198 
199         const PODVector<GlyphLocation>& pageGlyphLocation = pageGlyphLocations_[n];
200 
201         switch (textEffect)
202         {
203         case TE_NONE:
204             ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
205             break;
206 
207         case TE_SHADOW:
208             ConstructBatch(pageBatch, pageGlyphLocation, shadowOffset_.x_, shadowOffset_.y_, &effectColor_, effectDepthBias_);
209             ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
210             break;
211 
212         case TE_STROKE:
213             if (roundStroke_)
214             {
215                 // Samples should be even or glyph may be redrawn in wrong x y pos making stroke corners rough
216                 // Adding to thickness helps with thickness of 1 not having enought samples for this formula
217                 // or certain fonts with reflex corners requiring more glyph samples for a smooth stroke when large
218                 int thickness = Min(strokeThickness_, fontSize_);
219                 int samples = thickness * thickness + (thickness % 2 == 0 ? 4 : 3);
220                 float angle = 360.f / samples;
221                 float floatThickness = (float)thickness;
222                 for (int i = 0; i < samples; ++i)
223                 {
224                     float x = Cos(angle * i) * floatThickness;
225                     float y = Sin(angle * i) * floatThickness;
226                     ConstructBatch(pageBatch, pageGlyphLocation, x, y, &effectColor_, effectDepthBias_);
227                 }
228             }
229             else
230             {
231                 int thickness = Min(strokeThickness_, fontSize_);
232                 int x, y;
233                 for (x = -thickness; x <= thickness; ++x)
234                 {
235                     for (y = -thickness; y <= thickness; ++y)
236                     {
237                         // Don't draw glyphs that aren't on the edges
238                         if (x > -thickness && x < thickness &&
239                             y > -thickness && y < thickness)
240                             continue;
241 
242                         ConstructBatch(pageBatch, pageGlyphLocation, x, y, &effectColor_, effectDepthBias_);
243                     }
244                 }
245             }
246             ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
247             break;
248         }
249 
250         UIBatch::AddOrMerge(pageBatch, batches);
251     }
252 
253     // Reset hovering for next frame
254     hovering_ = false;
255 }
256 
OnResize(const IntVector2 & newSize,const IntVector2 & delta)257 void Text::OnResize(const IntVector2& newSize, const IntVector2& delta)
258 {
259     if (wordWrap_)
260         UpdateText(true);
261     else
262         charLocationsDirty_ = true;
263 }
264 
OnIndentSet()265 void Text::OnIndentSet()
266 {
267     charLocationsDirty_ = true;
268 }
269 
SetFont(const String & fontName,int size)270 bool Text::SetFont(const String& fontName, int size)
271 {
272     ResourceCache* cache = GetSubsystem<ResourceCache>();
273     return SetFont(cache->GetResource<Font>(fontName), size);
274 }
275 
SetFont(Font * font,float size)276 bool Text::SetFont(Font* font, float size)
277 {
278     if (!font)
279     {
280         URHO3D_LOGERROR("Null font for Text");
281         return false;
282     }
283 
284     if (font != font_ || size != fontSize_)
285     {
286         font_ = font;
287         fontSize_ = Max(size, 1);
288         UpdateText();
289     }
290 
291     return true;
292 }
293 
SetFontSize(float size)294 bool Text::SetFontSize(float size)
295 {
296     // Initial font must be set
297     if (!font_)
298         return false;
299     else
300         return SetFont(font_, size);
301 }
302 
DecodeToUnicode()303 void Text::DecodeToUnicode()
304 {
305     unicodeText_.Clear();
306     for (unsigned i = 0; i < text_.Length();)
307         unicodeText_.Push(text_.NextUTF8Char(i));
308 }
309 
SetText(const String & text)310 void Text::SetText(const String& text)
311 {
312     if (autoLocalizable_)
313     {
314         stringId_ = text;
315         Localization* l10n = GetSubsystem<Localization>();
316         text_ = l10n->Get(stringId_);
317     }
318     else
319     {
320         text_ = text;
321     }
322 
323     DecodeToUnicode();
324     ValidateSelection();
325     UpdateText();
326 }
327 
SetTextAlignment(HorizontalAlignment align)328 void Text::SetTextAlignment(HorizontalAlignment align)
329 {
330     if (align != textAlignment_)
331     {
332         textAlignment_ = align;
333         charLocationsDirty_ = true;
334     }
335 }
336 
SetRowSpacing(float spacing)337 void Text::SetRowSpacing(float spacing)
338 {
339     if (spacing != rowSpacing_)
340     {
341         rowSpacing_ = Max(spacing, MIN_ROW_SPACING);
342         UpdateText();
343     }
344 }
345 
SetWordwrap(bool enable)346 void Text::SetWordwrap(bool enable)
347 {
348     if (enable != wordWrap_)
349     {
350         wordWrap_ = enable;
351         UpdateText();
352     }
353 }
354 
SetAutoLocalizable(bool enable)355 void Text::SetAutoLocalizable(bool enable)
356 {
357     if (enable != autoLocalizable_)
358     {
359         autoLocalizable_ = enable;
360         if (enable)
361         {
362             stringId_ = text_;
363             Localization* l10n = GetSubsystem<Localization>();
364             text_ = l10n->Get(stringId_);
365             SubscribeToEvent(E_CHANGELANGUAGE, URHO3D_HANDLER(Text, HandleChangeLanguage));
366         }
367         else
368         {
369             text_ = stringId_;
370             stringId_ = "";
371             UnsubscribeFromEvent(E_CHANGELANGUAGE);
372         }
373         DecodeToUnicode();
374         ValidateSelection();
375         UpdateText();
376     }
377 }
378 
HandleChangeLanguage(StringHash eventType,VariantMap & eventData)379 void Text::HandleChangeLanguage(StringHash eventType, VariantMap& eventData)
380 {
381     Localization* l10n = GetSubsystem<Localization>();
382     text_ = l10n->Get(stringId_);
383     DecodeToUnicode();
384     ValidateSelection();
385     UpdateText();
386 }
387 
SetSelection(unsigned start,unsigned length)388 void Text::SetSelection(unsigned start, unsigned length)
389 {
390     selectionStart_ = start;
391     selectionLength_ = length;
392     ValidateSelection();
393 }
394 
ClearSelection()395 void Text::ClearSelection()
396 {
397     selectionStart_ = 0;
398     selectionLength_ = 0;
399 }
400 
SetSelectionColor(const Color & color)401 void Text::SetSelectionColor(const Color& color)
402 {
403     selectionColor_ = color;
404 }
405 
SetHoverColor(const Color & color)406 void Text::SetHoverColor(const Color& color)
407 {
408     hoverColor_ = color;
409 }
410 
SetTextEffect(TextEffect textEffect)411 void Text::SetTextEffect(TextEffect textEffect)
412 {
413     textEffect_ = textEffect;
414 }
415 
SetEffectShadowOffset(const IntVector2 & offset)416 void Text::SetEffectShadowOffset(const IntVector2& offset)
417 {
418     shadowOffset_ = offset;
419 }
420 
SetEffectStrokeThickness(int thickness)421 void Text::SetEffectStrokeThickness(int thickness)
422 {
423     strokeThickness_ = Abs(thickness);
424 }
425 
SetEffectRoundStroke(bool roundStroke)426 void Text::SetEffectRoundStroke(bool roundStroke)
427 {
428     roundStroke_ = roundStroke;
429 }
430 
SetEffectColor(const Color & effectColor)431 void Text::SetEffectColor(const Color& effectColor)
432 {
433     effectColor_ = effectColor;
434 }
435 
SetEffectDepthBias(float bias)436 void Text::SetEffectDepthBias(float bias)
437 {
438     effectDepthBias_ = bias;
439 }
440 
GetRowWidth(unsigned index) const441 float Text::GetRowWidth(unsigned index) const
442 {
443     return index < rowWidths_.Size() ? rowWidths_[index] : 0;
444 }
445 
GetCharPosition(unsigned index)446 Vector2 Text::GetCharPosition(unsigned index)
447 {
448     if (charLocationsDirty_)
449         UpdateCharLocations();
450     if (charLocations_.Empty())
451         return Vector2::ZERO;
452     // For convenience, return the position of the text ending if index exceeded
453     if (index > charLocations_.Size() - 1)
454         index = charLocations_.Size() - 1;
455     return charLocations_[index].position_;
456 }
457 
GetCharSize(unsigned index)458 Vector2 Text::GetCharSize(unsigned index)
459 {
460     if (charLocationsDirty_)
461         UpdateCharLocations();
462     if (charLocations_.Size() < 2)
463         return Vector2::ZERO;
464     // For convenience, return the size of the last char if index exceeded (last size entry is zero)
465     if (index > charLocations_.Size() - 2)
466         index = charLocations_.Size() - 2;
467     return charLocations_[index].size_;
468 }
469 
SetFontAttr(const ResourceRef & value)470 void Text::SetFontAttr(const ResourceRef& value)
471 {
472     ResourceCache* cache = GetSubsystem<ResourceCache>();
473     font_ = cache->GetResource<Font>(value.name_);
474 }
475 
GetFontAttr() const476 ResourceRef Text::GetFontAttr() const
477 {
478     return GetResourceRef(font_, Font::GetTypeStatic());
479 }
480 
SetTextAttr(const String & value)481 void Text::SetTextAttr(const String& value)
482 {
483     text_ = value;
484     if (autoLocalizable_)
485         stringId_ = value;
486 }
487 
GetTextAttr() const488 String Text::GetTextAttr() const
489 {
490     if (autoLocalizable_ && stringId_.Length())
491         return stringId_;
492     else
493         return text_;
494 }
495 
FilterImplicitAttributes(XMLElement & dest) const496 bool Text::FilterImplicitAttributes(XMLElement& dest) const
497 {
498     if (!UIElement::FilterImplicitAttributes(dest))
499         return false;
500 
501     if (!IsFixedWidth())
502     {
503         if (!RemoveChildXML(dest, "Size"))
504             return false;
505         if (!RemoveChildXML(dest, "Min Size"))
506             return false;
507         if (!RemoveChildXML(dest, "Max Size"))
508             return false;
509     }
510 
511     return true;
512 }
513 
UpdateText(bool onResize)514 void Text::UpdateText(bool onResize)
515 {
516     rowWidths_.Clear();
517     printText_.Clear();
518 
519     if (font_)
520     {
521         FontFace* face = font_->GetFace(fontSize_);
522         if (!face)
523             return;
524 
525         rowHeight_ = face->GetRowHeight();
526 
527         int width = 0;
528         int height = 0;
529         int rowWidth = 0;
530         int rowHeight = (int)(rowSpacing_ * rowHeight_ + 0.5f);
531 
532         // First see if the text must be split up
533         if (!wordWrap_)
534         {
535             printText_ = unicodeText_;
536             printToText_.Resize(printText_.Size());
537             for (unsigned i = 0; i < printText_.Size(); ++i)
538                 printToText_[i] = i;
539         }
540         else
541         {
542             int maxWidth = GetWidth();
543             unsigned nextBreak = 0;
544             unsigned lineStart = 0;
545             printToText_.Clear();
546 
547             for (unsigned i = 0; i < unicodeText_.Size(); ++i)
548             {
549                 unsigned j;
550                 unsigned c = unicodeText_[i];
551 
552                 if (c != '\n')
553                 {
554                     bool ok = true;
555 
556                     if (nextBreak <= i)
557                     {
558                         int futureRowWidth = rowWidth;
559                         for (j = i; j < unicodeText_.Size(); ++j)
560                         {
561                             unsigned d = unicodeText_[j];
562                             if (d == ' ' || d == '\n')
563                             {
564                                 nextBreak = j;
565                                 break;
566                             }
567                             const FontGlyph* glyph = face->GetGlyph(d);
568                             if (glyph)
569                             {
570                                 futureRowWidth += glyph->advanceX_;
571                                 if (j < unicodeText_.Size() - 1)
572                                     futureRowWidth += face->GetKerning(d, unicodeText_[j + 1]);
573                             }
574                             if (d == '-' && futureRowWidth <= maxWidth)
575                             {
576                                 nextBreak = j + 1;
577                                 break;
578                             }
579                             if (futureRowWidth > maxWidth)
580                             {
581                                 ok = false;
582                                 break;
583                             }
584                         }
585                     }
586 
587                     if (!ok)
588                     {
589                         // If did not find any breaks on the line, copy until j, or at least 1 char, to prevent infinite loop
590                         if (nextBreak == lineStart)
591                         {
592                             while (i < j)
593                             {
594                                 printText_.Push(unicodeText_[i]);
595                                 printToText_.Push(i);
596                                 ++i;
597                             }
598                         }
599                         // Eliminate spaces that have been copied before the forced break
600                         while (printText_.Size() && printText_.Back() == ' ')
601                         {
602                             printText_.Pop();
603                             printToText_.Pop();
604                         }
605                         printText_.Push('\n');
606                         printToText_.Push(Min(i, unicodeText_.Size() - 1));
607                         rowWidth = 0;
608                         nextBreak = lineStart = i;
609                     }
610 
611                     if (i < unicodeText_.Size())
612                     {
613                         // When copying a space, position is allowed to be over row width
614                         c = unicodeText_[i];
615                         const FontGlyph* glyph = face->GetGlyph(c);
616                         if (glyph)
617                         {
618                             rowWidth += glyph->advanceX_;
619                             if (i < unicodeText_.Size() - 1)
620                                 rowWidth += face->GetKerning(c, unicodeText_[i + 1]);
621                         }
622                         if (rowWidth <= maxWidth)
623                         {
624                             printText_.Push(c);
625                             printToText_.Push(i);
626                         }
627                     }
628                 }
629                 else
630                 {
631                     printText_.Push('\n');
632                     printToText_.Push(Min(i, unicodeText_.Size() - 1));
633                     rowWidth = 0;
634                     nextBreak = lineStart = i;
635                 }
636             }
637         }
638 
639         rowWidth = 0;
640 
641         for (unsigned i = 0; i < printText_.Size(); ++i)
642         {
643             unsigned c = printText_[i];
644 
645             if (c != '\n')
646             {
647                 const FontGlyph* glyph = face->GetGlyph(c);
648                 if (glyph)
649                 {
650                     rowWidth += glyph->advanceX_;
651                     if (i < printText_.Size() - 1)
652                         rowWidth += face->GetKerning(c, printText_[i + 1]);
653                 }
654             }
655             else
656             {
657                 width = Max(width, rowWidth);
658                 height += rowHeight;
659                 rowWidths_.Push(rowWidth);
660                 rowWidth = 0;
661             }
662         }
663 
664         if (rowWidth)
665         {
666             width = Max(width, rowWidth);
667             height += rowHeight;
668             rowWidths_.Push(rowWidth);
669         }
670 
671         // Set at least one row height even if text is empty
672         if (!height)
673             height = rowHeight;
674 
675         // Set minimum and current size according to the text size, but respect fixed width if set
676         if (!IsFixedWidth())
677         {
678             if (wordWrap_)
679                 SetMinWidth(0);
680             else
681             {
682                 SetMinWidth(width);
683                 SetWidth(width);
684             }
685         }
686         SetFixedHeight(height);
687 
688         charLocationsDirty_ = true;
689     }
690     else
691     {
692         // No font, nothing to render
693         pageGlyphLocations_.Clear();
694     }
695 
696     // If wordwrap is on, parent may need layout update to correct for overshoot in size. However, do not do this when the
697     // update is a response to resize, as that could cause infinite recursion
698     if (wordWrap_ && !onResize)
699     {
700         UIElement* parent = GetParent();
701         if (parent && parent->GetLayoutMode() != LM_FREE)
702             parent->UpdateLayout();
703     }
704 }
705 
UpdateCharLocations()706 void Text::UpdateCharLocations()
707 {
708     // Remember the font face to see if it's still valid when it's time to render
709     FontFace* face = font_ ? font_->GetFace(fontSize_) : (FontFace*)0;
710     if (!face)
711         return;
712     fontFace_ = face;
713 
714     int rowHeight = (int)(rowSpacing_ * rowHeight_ + 0.5f);
715 
716     // Store position & size of each character, and locations per texture page
717     unsigned numChars = unicodeText_.Size();
718     charLocations_.Resize(numChars + 1);
719     pageGlyphLocations_.Resize(face->GetTextures().Size());
720     for (unsigned i = 0; i < pageGlyphLocations_.Size(); ++i)
721         pageGlyphLocations_[i].Clear();
722 
723     IntVector2 offset = font_->GetTotalGlyphOffset(fontSize_);
724 
725     unsigned rowIndex = 0;
726     unsigned lastFilled = 0;
727     float x = floor(GetRowStartPosition(rowIndex) + offset.x_ + 0.5f);
728     float y = floor(offset.y_ + 0.5f);
729 
730     for (unsigned i = 0; i < printText_.Size(); ++i)
731     {
732         CharLocation loc;
733         loc.position_ = Vector2(x, y);
734 
735         unsigned c = printText_[i];
736         if (c != '\n')
737         {
738             const FontGlyph* glyph = face->GetGlyph(c);
739             loc.size_ = Vector2(glyph ? glyph->advanceX_ : 0, rowHeight_);
740             if (glyph)
741             {
742                 // Store glyph's location for rendering. Verify that glyph page is valid
743                 if (glyph->page_ < pageGlyphLocations_.Size())
744                     pageGlyphLocations_[glyph->page_].Push(GlyphLocation(x, y, glyph));
745                 x += glyph->advanceX_;
746                 if (i < printText_.Size() - 1)
747                     x += face->GetKerning(c, printText_[i + 1]);
748             }
749         }
750         else
751         {
752             loc.size_ = Vector2::ZERO;
753             x = GetRowStartPosition(++rowIndex);
754             y += rowHeight;
755         }
756 
757         if (lastFilled > printToText_[i])
758             lastFilled = printToText_[i];
759 
760         // Fill gaps in case characters were skipped from printing
761         for (unsigned j = lastFilled; j <= printToText_[i]; ++j)
762             charLocations_[j] = loc;
763         lastFilled = printToText_[i] + 1;
764     }
765     // Store the ending position
766     charLocations_[numChars].position_ = Vector2(x, y);
767     charLocations_[numChars].size_ = Vector2::ZERO;
768 
769     charLocationsDirty_ = false;
770 }
771 
ValidateSelection()772 void Text::ValidateSelection()
773 {
774     unsigned textLength = unicodeText_.Size();
775 
776     if (textLength)
777     {
778         if (selectionStart_ >= textLength)
779             selectionStart_ = textLength - 1;
780         if (selectionStart_ + selectionLength_ > textLength)
781             selectionLength_ = textLength - selectionStart_;
782     }
783     else
784     {
785         selectionStart_ = 0;
786         selectionLength_ = 0;
787     }
788 }
789 
GetRowStartPosition(unsigned rowIndex) const790 int Text::GetRowStartPosition(unsigned rowIndex) const
791 {
792     float rowWidth = 0;
793 
794     if (rowIndex < rowWidths_.Size())
795         rowWidth = rowWidths_[rowIndex];
796 
797     int ret = GetIndentWidth();
798 
799     switch (textAlignment_)
800     {
801     case HA_LEFT:
802         break;
803     case HA_CENTER:
804         ret += (GetSize().x_ - rowWidth) / 2;
805         break;
806     case HA_RIGHT:
807         ret += GetSize().x_ - rowWidth;
808         break;
809     }
810 
811     return ret;
812 }
813 
ConstructBatch(UIBatch & pageBatch,const PODVector<GlyphLocation> & pageGlyphLocation,float dx,float dy,Color * color,float depthBias)814 void Text::ConstructBatch(UIBatch& pageBatch, const PODVector<GlyphLocation>& pageGlyphLocation, float dx, float dy, Color* color,
815     float depthBias)
816 {
817     unsigned startDataSize = pageBatch.vertexData_->Size();
818 
819     if (!color)
820         pageBatch.SetDefaultColor();
821     else
822         pageBatch.SetColor(*color);
823 
824     for (unsigned i = 0; i < pageGlyphLocation.Size(); ++i)
825     {
826         const GlyphLocation& glyphLocation = pageGlyphLocation[i];
827         const FontGlyph& glyph = *glyphLocation.glyph_;
828         pageBatch.AddQuad(dx + glyphLocation.x_ + glyph.offsetX_, dy + glyphLocation.y_ + glyph.offsetY_, glyph.width_,
829             glyph.height_, glyph.x_, glyph.y_, glyph.texWidth_, glyph.texHeight_);
830     }
831 
832     if (depthBias != 0.0f)
833     {
834         unsigned dataSize = pageBatch.vertexData_->Size();
835         for (unsigned i = startDataSize; i < dataSize; i += UI_VERTEX_SIZE)
836             pageBatch.vertexData_->At(i + 2) += depthBias;
837     }
838 }
839 
840 }
841