Text3DText.cpp 30 KB

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