Text3DText.cpp 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000
  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. SetMinWidth(wordWrap_ ? 0 : width);
  643. SetWidth(width);
  644. }
  645. SetFixedHeight(height);
  646. charLocationsDirty_ = true;
  647. }
  648. else
  649. {
  650. // No font, nothing to render
  651. pageGlyphLocations_.Clear();
  652. }
  653. }
  654. void Text3DText::UpdateCharLocations()
  655. {
  656. // Remember the font face to see if it's still valid when it's time to render
  657. Text3DFontFace* face = font_ ? font_->GetFace(fontSize_) : (Text3DFontFace*)0;
  658. if (!face)
  659. return;
  660. fontFace_ = face;
  661. int rowHeight = (int)(rowSpacing_ * rowHeight_ + 0.5f);
  662. // Store position & size of each character, and locations per texture page
  663. unsigned numChars = unicodeText_.Size();
  664. charLocations_.Resize(numChars + 1);
  665. pageGlyphLocations_.Resize(face->GetTextures().Size());
  666. for (unsigned i = 0; i < pageGlyphLocations_.Size(); ++i)
  667. pageGlyphLocations_[i].Clear();
  668. IntVector2 offset = font_->GetTotalGlyphOffset(fontSize_);
  669. unsigned rowIndex = 0;
  670. unsigned lastFilled = 0;
  671. float x = floor(GetRowStartPosition(rowIndex) + offset.x_ + 0.5f);
  672. float y = floor(offset.y_ + 0.5f);
  673. for (unsigned i = 0; i < printText_.Size(); ++i)
  674. {
  675. Text3DCharLocation loc;
  676. loc.position_ = Vector2(x, y);
  677. unsigned c = printText_[i];
  678. if (c != '\n')
  679. {
  680. const Text3DFontGlyph* glyph = face->GetGlyph(c);
  681. loc.size_ = Vector2(glyph ? glyph->advanceX_ : 0, rowHeight_);
  682. if (glyph)
  683. {
  684. // Store glyph's location for rendering. Verify that glyph page is valid
  685. if (glyph->page_ < pageGlyphLocations_.Size())
  686. pageGlyphLocations_[glyph->page_].Push(Text3DGlyphLocation(x, y, glyph));
  687. x += glyph->advanceX_;
  688. if (i < printText_.Size() - 1)
  689. x += face->GetKerning(c, printText_[i + 1]);
  690. }
  691. }
  692. else
  693. {
  694. loc.size_ = Vector2::ZERO;
  695. x = GetRowStartPosition(++rowIndex);
  696. y += rowHeight;
  697. }
  698. if (lastFilled > printToText_[i])
  699. lastFilled = printToText_[i];
  700. // Fill gaps in case characters were skipped from printing
  701. for (unsigned j = lastFilled; j <= printToText_[i]; ++j)
  702. charLocations_[j] = loc;
  703. lastFilled = printToText_[i] + 1;
  704. }
  705. // Store the ending position
  706. charLocations_[numChars].position_ = Vector2(x, y);
  707. charLocations_[numChars].size_ = Vector2::ZERO;
  708. charLocationsDirty_ = false;
  709. }
  710. void Text3DText::ValidateSelection()
  711. {
  712. unsigned textLength = unicodeText_.Size();
  713. if (textLength)
  714. {
  715. if (selectionStart_ >= textLength)
  716. selectionStart_ = textLength - 1;
  717. if (selectionStart_ + selectionLength_ > textLength)
  718. selectionLength_ = textLength - selectionStart_;
  719. }
  720. else
  721. {
  722. selectionStart_ = 0;
  723. selectionLength_ = 0;
  724. }
  725. }
  726. int Text3DText::GetRowStartPosition(unsigned rowIndex) const
  727. {
  728. float rowWidth = 0;
  729. if (rowIndex < rowWidths_.Size())
  730. rowWidth = rowWidths_[rowIndex];
  731. int ret = GetIndentWidth();
  732. switch (textAlignment_)
  733. {
  734. case HA_LEFT:
  735. break;
  736. case HA_CENTER:
  737. ret += (GetSize().x_ - rowWidth) / 2;
  738. break;
  739. case HA_RIGHT:
  740. ret += GetSize().x_ - rowWidth;
  741. break;
  742. }
  743. return ret;
  744. }
  745. void Text3DText::SetIndent(int indent)
  746. {
  747. indent_ = indent;
  748. OnIndentSet();
  749. }
  750. void Text3DText::SetIndentSpacing(int indentSpacing)
  751. {
  752. indentSpacing_ = Max(indentSpacing, 0);
  753. OnIndentSet();
  754. }
  755. void Text3DText::ConstructBatch(Text3DBatch& pageBatch, const PODVector<Text3DGlyphLocation>& pageGlyphLocation, float dx, float dy, Color* color,
  756. float depthBias)
  757. {
  758. unsigned startDataSize = pageBatch.vertexData_->Size();
  759. if (!color)
  760. pageBatch.SetDefaultColor();
  761. else
  762. pageBatch.SetColor(*color);
  763. for (unsigned i = 0; i < pageGlyphLocation.Size(); ++i)
  764. {
  765. const Text3DGlyphLocation& glyphLocation = pageGlyphLocation[i];
  766. const Text3DFontGlyph& glyph = *glyphLocation.glyph_;
  767. pageBatch.AddQuad(dx + glyphLocation.x_ + glyph.offsetX_, dy + glyphLocation.y_ + glyph.offsetY_, glyph.width_,
  768. glyph.height_, glyph.x_, glyph.y_);
  769. }
  770. if (depthBias != 0.0f)
  771. {
  772. unsigned dataSize = pageBatch.vertexData_->Size();
  773. for (unsigned i = startDataSize; i < dataSize; i += TEXT3D_VERTEX_SIZE)
  774. pageBatch.vertexData_->At(i + 2) += depthBias;
  775. }
  776. }
  777. void Text3DText::HandlePostUpdate(StringHash eventType, VariantMap& eventData)
  778. {
  779. using namespace PostUpdate;
  780. UpdateAttributeAnimations(eventData[P_TIMESTEP].GetFloat());
  781. }
  782. void Text3DText::SetSelected(bool enable)
  783. {
  784. selected_ = enable;
  785. }
  786. void Text3DText::SetHovering(bool enable)
  787. {
  788. hovering_ = enable;
  789. }
  790. void Text3DText::SetWidth(int width)
  791. {
  792. SetSize(IntVector2(width, size_.y_));
  793. }
  794. void Text3DText::SetHeight(int height)
  795. {
  796. SetSize(IntVector2(size_.x_, height));
  797. }
  798. void Text3DText::SetMinSize(const IntVector2& minSize)
  799. {
  800. minSize_.x_ = Max(minSize.x_, 0);
  801. minSize_.y_ = Max(minSize.y_, 0);
  802. SetSize(size_);
  803. }
  804. void Text3DText::SetMinSize(int width, int height)
  805. {
  806. SetMinSize(IntVector2(width, height));
  807. }
  808. void Text3DText::SetMinWidth(int width)
  809. {
  810. SetMinSize(IntVector2(width, minSize_.y_));
  811. }
  812. void Text3DText::SetMinHeight(int height)
  813. {
  814. SetMinSize(IntVector2(minSize_.x_, height));
  815. }
  816. void Text3DText::SetMaxSize(const IntVector2& maxSize)
  817. {
  818. maxSize_.x_ = Max(maxSize.x_, 0);
  819. maxSize_.y_ = Max(maxSize.y_, 0);
  820. SetSize(size_);
  821. }
  822. void Text3DText::SetMaxSize(int width, int height)
  823. {
  824. SetMaxSize(IntVector2(width, height));
  825. }
  826. void Text3DText::SetMaxWidth(int width)
  827. {
  828. SetMaxSize(IntVector2(width, maxSize_.y_));
  829. }
  830. void Text3DText::SetMaxHeight(int height)
  831. {
  832. SetMaxSize(IntVector2(maxSize_.x_, height));
  833. }
  834. void Text3DText::SetFixedSize(const IntVector2& size)
  835. {
  836. minSize_ = maxSize_ = IntVector2(Max(size.x_, 0), Max(size.y_, 0));
  837. SetSize(size);
  838. }
  839. void Text3DText::SetFixedSize(int width, int height)
  840. {
  841. SetFixedSize(IntVector2(width, height));
  842. }
  843. void Text3DText::SetFixedWidth(int width)
  844. {
  845. minSize_.x_ = maxSize_.x_ = Max(width, 0);
  846. SetWidth(width);
  847. }
  848. void Text3DText::SetFixedHeight(int height)
  849. {
  850. minSize_.y_ = maxSize_.y_ = Max(height, 0);
  851. SetHeight(height);
  852. }
  853. void Text3DText::SetSize(const IntVector2& size)
  854. {
  855. IntVector2 oldSize = size_;
  856. size_ = size;
  857. IntVector2 delta = size_ - oldSize;
  858. MarkDirty();
  859. OnResize(size_, delta);
  860. }
  861. }