Text3DText.cpp 29 KB

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