UIRichText.cpp 73 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974
  1. /****************************************************************************
  2. Copyright (c) 2013 cocos2d-x.org
  3. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
  4. http://www.cocos2d-x.org
  5. Permission is hereby granted, free of charge, to any person obtaining a copy
  6. of this software and associated documentation files (the "Software"), to deal
  7. in the Software without restriction, including without limitation the rights
  8. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. copies of the Software, and to permit persons to whom the Software is
  10. furnished to do so, subject to the following conditions:
  11. The above copyright notice and this permission notice shall be included in
  12. all copies or substantial portions of the Software.
  13. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. THE SOFTWARE.
  20. ****************************************************************************/
  21. #include "ui/UIRichText.h"
  22. #include <sstream>
  23. #include <vector>
  24. #include <locale>
  25. #include <algorithm>
  26. #include "platform/CCFileUtils.h"
  27. #include "platform/CCApplication.h"
  28. #include "base/CCEventListenerTouch.h"
  29. #include "base/CCEventDispatcher.h"
  30. #include "base/CCDirector.h"
  31. #include "2d/CCLabel.h"
  32. #include "2d/CCSprite.h"
  33. #include "base/ccUTF8.h"
  34. #include "ui/UIHelper.h"
  35. #include "platform/CCSAXParser.h"
  36. USING_NS_CC;
  37. using namespace cocos2d::ui;
  38. class ListenerComponent : public Component
  39. {
  40. public:
  41. static const std::string COMPONENT_NAME; /*!< component name */
  42. static ListenerComponent* create(Node* parent, const std::string& url, const RichText::OpenUrlHandler handleOpenUrl = nullptr)
  43. {
  44. auto component = new (std::nothrow) ListenerComponent(parent, url, handleOpenUrl);
  45. component->autorelease();
  46. return component;
  47. }
  48. explicit ListenerComponent(Node* parent, const std::string& url, const RichText::OpenUrlHandler handleOpenUrl)
  49. : _parent(parent)
  50. , _url(url)
  51. , _handleOpenUrl(handleOpenUrl)
  52. {
  53. setName(ListenerComponent::COMPONENT_NAME);
  54. _touchListener = cocos2d::EventListenerTouchAllAtOnce::create();
  55. _touchListener->onTouchesEnded = CC_CALLBACK_2(ListenerComponent::onTouchesEnded, this);
  56. Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(_touchListener, _parent);
  57. _touchListener->retain();
  58. }
  59. virtual ~ListenerComponent()
  60. {
  61. Director::getInstance()->getEventDispatcher()->removeEventListener(_touchListener);
  62. _touchListener->release();
  63. }
  64. void onTouchesEnded(const std::vector<Touch*>& touches, Event* /*event*/)
  65. {
  66. for (const auto& touch: touches)
  67. {
  68. // FIXME: Node::getBoundBox() doesn't return it in local coordinates... so create one manually.
  69. Rect localRect = Rect(Vec2::ZERO, _parent->getContentSize());
  70. if (localRect.containsPoint(_parent->convertTouchToNodeSpace(touch))) {
  71. if (_handleOpenUrl) {
  72. _handleOpenUrl(_url);
  73. }
  74. }
  75. }
  76. }
  77. void setOpenUrlHandler(const RichText::OpenUrlHandler& handleOpenUrl)
  78. {
  79. _handleOpenUrl = handleOpenUrl;
  80. }
  81. private:
  82. Node* _parent; // weak ref.
  83. std::string _url;
  84. RichText::OpenUrlHandler _handleOpenUrl;
  85. EventDispatcher* _eventDispatcher; // weak ref.
  86. EventListenerTouchAllAtOnce* _touchListener; // strong ref.
  87. };
  88. const std::string ListenerComponent::COMPONENT_NAME("cocos2d_ui_UIRichText_ListenerComponent");
  89. bool RichElement::init(int tag, const Color3B &color, GLubyte opacity)
  90. {
  91. _tag = tag;
  92. _color = color;
  93. _opacity = opacity;
  94. return true;
  95. }
  96. bool RichElement::equalType(Type type)
  97. {
  98. return (_type == type);
  99. }
  100. void RichElement::setColor(const Color3B& color)
  101. {
  102. _color = color;
  103. }
  104. RichElementText* RichElementText::create(int tag, const Color3B &color, GLubyte opacity, const std::string& text,
  105. const std::string& fontName, float fontSize, uint32_t flags, const std::string& url,
  106. const Color3B& outlineColor, int outlineSize ,
  107. const Color3B& shadowColor, const cocos2d::Size& shadowOffset, int shadowBlurRadius,
  108. const Color3B& glowColor)
  109. {
  110. RichElementText* element = new (std::nothrow) RichElementText();
  111. if (element && element->init(tag, color, opacity, text, fontName, fontSize, flags, url,
  112. outlineColor, outlineSize, shadowColor, shadowOffset, shadowBlurRadius, glowColor))
  113. {
  114. element->autorelease();
  115. return element;
  116. }
  117. CC_SAFE_DELETE(element);
  118. return nullptr;
  119. }
  120. bool RichElementText::init(int tag, const Color3B &color, GLubyte opacity, const std::string& text,
  121. const std::string& fontName, float fontSize, uint32_t flags, const std::string& url,
  122. const Color3B& outlineColor, int outlineSize ,
  123. const Color3B& shadowColor, const cocos2d::Size& shadowOffset, int shadowBlurRadius,
  124. const Color3B& glowColor)
  125. {
  126. if (RichElement::init(tag, color, opacity))
  127. {
  128. _text = text;
  129. _fontName = fontName;
  130. _fontSize = fontSize;
  131. _flags = flags;
  132. _url = url;
  133. _outlineColor = outlineColor;
  134. _outlineSize = outlineSize;
  135. _shadowColor = shadowColor;
  136. _shadowOffset = shadowOffset;
  137. _shadowBlurRadius = shadowBlurRadius;
  138. _glowColor = glowColor;
  139. _isNewLine = false;
  140. _maxNewLine = 0;
  141. return true;
  142. }
  143. return false;
  144. }
  145. RichElementImage* RichElementImage::create(int tag, const Color3B &color, GLubyte opacity, const std::string& filePath, const std::string& url, Widget::TextureResType texType)
  146. {
  147. RichElementImage* element = new (std::nothrow) RichElementImage();
  148. if (element && element->init(tag, color, opacity, filePath, url, texType))
  149. {
  150. element->autorelease();
  151. return element;
  152. }
  153. CC_SAFE_DELETE(element);
  154. return nullptr;
  155. }
  156. bool RichElementImage::init(int tag, const Color3B &color, GLubyte opacity, const std::string& filePath, const std::string& url, Widget::TextureResType texType)
  157. {
  158. if (RichElement::init(tag, color, opacity))
  159. {
  160. _filePath = filePath;
  161. _width = -1;
  162. _height = -1;
  163. _url = url;
  164. _textureType = texType;
  165. return true;
  166. }
  167. return false;
  168. }
  169. void RichElementImage::setWidth(int width)
  170. {
  171. _width = width;
  172. }
  173. void RichElementImage::setHeight(int height)
  174. {
  175. _height = height;
  176. }
  177. void RichElementImage::setUrl(const std::string& url)
  178. {
  179. _url = url;
  180. }
  181. RichElementCustomNode* RichElementCustomNode::create(int tag, const Color3B &color, GLubyte opacity, cocos2d::Node *customNode)
  182. {
  183. RichElementCustomNode* element = new (std::nothrow) RichElementCustomNode();
  184. if (element && element->init(tag, color, opacity, customNode))
  185. {
  186. element->autorelease();
  187. return element;
  188. }
  189. CC_SAFE_DELETE(element);
  190. return nullptr;
  191. }
  192. bool RichElementCustomNode::init(int tag, const Color3B &color, GLubyte opacity, cocos2d::Node *customNode)
  193. {
  194. if (RichElement::init(tag, color, opacity))
  195. {
  196. _customNode = customNode;
  197. _customNode->retain();
  198. return true;
  199. }
  200. return false;
  201. }
  202. RichElementNewLine* RichElementNewLine::create(int tag, const Color3B& color, GLubyte opacity)
  203. {
  204. RichElementNewLine* element = new (std::nothrow) RichElementNewLine();
  205. if (element && element->init(tag, color, opacity))
  206. {
  207. element->autorelease();
  208. return element;
  209. }
  210. CC_SAFE_DELETE(element);
  211. return nullptr;
  212. }
  213. /** @brief parse a XML. */
  214. class MyXMLVisitor : public SAXDelegator
  215. {
  216. public:
  217. /** @brief underline or strikethrough */
  218. enum class StyleLine {
  219. NONE,
  220. UNDERLINE, /*!< underline */
  221. STRIKETHROUGH /*!< a typographical presentation of words with a horizontal line through their center */
  222. };
  223. /** @brief outline, shadow or glow */
  224. enum class StyleEffect {
  225. NONE,
  226. OUTLINE, /*!< outline effect enabled */
  227. SHADOW, /*!< shadow effect enabled */
  228. GLOW /*!< glow effect enabled @discussion Limiting use to only when the Label created with true type font. */
  229. };
  230. /** @brief the attributes of text tag */
  231. struct Attributes
  232. {
  233. std::string face; /*!< font name */
  234. std::string url; /*!< url is a attribute of a anchor tag */
  235. float fontSize; /*!< font size */
  236. Color3B color; /*!< font color */
  237. bool hasColor; /*!< or color is specified? */
  238. bool bold; /*!< bold text */
  239. bool italics; /*!< italic text */
  240. StyleLine line; /*!< underline or strikethrough */
  241. StyleEffect effect; /*!< outline, shadow or glow */
  242. Color3B outlineColor; /*!< the color of the outline */
  243. int outlineSize; /*!< the outline effect size value */
  244. Color3B shadowColor; /*!< the shadow effect color value */
  245. cocos2d::Size shadowOffset; /*!< shadow effect offset value */
  246. int shadowBlurRadius; /*!< the shadow effect blur radius */
  247. Color3B glowColor; /*!< the glow effect color value */
  248. void setColor(const Color3B& acolor)
  249. {
  250. color = acolor;
  251. hasColor = true;
  252. }
  253. Attributes()
  254. : fontSize(-1)
  255. , hasColor(false)
  256. , bold(false)
  257. , italics(false)
  258. , line(StyleLine::NONE)
  259. , effect(StyleEffect::NONE)
  260. {
  261. }
  262. };
  263. private:
  264. std::vector<Attributes> _fontElements;
  265. RichText* _richText;
  266. struct TagBehavior {
  267. bool isFontElement;
  268. RichText::VisitEnterHandler handleVisitEnter;
  269. };
  270. typedef std::unordered_map<std::string, TagBehavior> TagTables;
  271. static TagTables _tagTables;
  272. public:
  273. explicit MyXMLVisitor(RichText* richText);
  274. virtual ~MyXMLVisitor();
  275. Color3B getColor() const;
  276. float getFontSize() const;
  277. std::string getFace() const;
  278. std::string getURL() const;
  279. bool getBold() const;
  280. bool getItalics() const;
  281. bool getUnderline() const;
  282. bool getStrikethrough() const;
  283. std::tuple<bool, Color3B, int> getOutline() const;
  284. std::tuple<bool, Color3B, cocos2d::Size, int> getShadow() const;
  285. std::tuple<bool, Color3B> getGlow() const;
  286. void startElement(void *ctx, const char *name, const char **atts) override;
  287. void endElement(void *ctx, const char *name) override;
  288. void textHandler(void *ctx, const char *s, size_t len) override;
  289. void pushBackFontElement(const Attributes& attribs);
  290. void popBackFontElement();
  291. void pushBackElement(RichElement* element);
  292. static void setTagDescription(const std::string& tag, bool isFontElement, RichText::VisitEnterHandler handleVisitEnter);
  293. static void removeTagDescription(const std::string& tag);
  294. private:
  295. ValueMap tagAttrMapWithXMLElement(const char ** attrs);
  296. };
  297. MyXMLVisitor::TagTables MyXMLVisitor::_tagTables;
  298. MyXMLVisitor::MyXMLVisitor(RichText* richText)
  299. : _fontElements(20)
  300. , _richText(richText)
  301. {
  302. MyXMLVisitor::setTagDescription("font", true, [](const ValueMap& tagAttrValueMap) {
  303. // supported attributes:
  304. // size, color, align, face
  305. ValueMap attrValueMap;
  306. if (tagAttrValueMap.find("size") != tagAttrValueMap.end()) {
  307. attrValueMap[RichText::KEY_FONT_SIZE] = tagAttrValueMap.at("size").asString();
  308. }
  309. if (tagAttrValueMap.find("color") != tagAttrValueMap.end()) {
  310. attrValueMap[RichText::KEY_FONT_COLOR_STRING] = tagAttrValueMap.at("color").asString();
  311. }
  312. if (tagAttrValueMap.find("face") != tagAttrValueMap.end()) {
  313. attrValueMap[RichText::KEY_FONT_FACE] = tagAttrValueMap.at("face").asString();
  314. }
  315. return make_pair(attrValueMap, nullptr);
  316. });
  317. MyXMLVisitor::setTagDescription("b", true, [](const ValueMap& /*tagAttrValueMap*/) {
  318. // no supported attributes
  319. ValueMap attrValueMap;
  320. attrValueMap[RichText::KEY_TEXT_BOLD] = true;
  321. return make_pair(attrValueMap, nullptr);
  322. });
  323. MyXMLVisitor::setTagDescription("i", true, [](const ValueMap& /*tagAttrValueMap*/) {
  324. // no supported attributes
  325. ValueMap attrValueMap;
  326. attrValueMap[RichText::KEY_TEXT_ITALIC] = true;
  327. return make_pair(attrValueMap, nullptr);
  328. });
  329. MyXMLVisitor::setTagDescription("del", true, [](const ValueMap& /*tagAttrValueMap*/) {
  330. // no supported attributes
  331. ValueMap attrValueMap;
  332. attrValueMap[RichText::KEY_TEXT_LINE] = RichText::VALUE_TEXT_LINE_DEL;
  333. return make_pair(attrValueMap, nullptr);
  334. });
  335. MyXMLVisitor::setTagDescription("u", true, [](const ValueMap& /*tagAttrValueMap*/) {
  336. // no supported attributes
  337. ValueMap attrValueMap;
  338. attrValueMap[RichText::KEY_TEXT_LINE] = RichText::VALUE_TEXT_LINE_UNDER;
  339. return make_pair(attrValueMap, nullptr);
  340. });
  341. MyXMLVisitor::setTagDescription("small", true, [](const ValueMap& /*tagAttrValueMap*/) {
  342. ValueMap attrValueMap;
  343. attrValueMap[RichText::KEY_FONT_SMALL] = true;
  344. return make_pair(attrValueMap, nullptr);
  345. });
  346. MyXMLVisitor::setTagDescription("big", true, [](const ValueMap& /*tagAttrValueMap*/) {
  347. ValueMap attrValueMap;
  348. attrValueMap[RichText::KEY_FONT_BIG] = true;
  349. return make_pair(attrValueMap, nullptr);
  350. });
  351. MyXMLVisitor::setTagDescription("img", false, [](const ValueMap& tagAttrValueMap) {
  352. // supported attributes:
  353. // src, height, width
  354. std::string src;
  355. int height = -1;
  356. int width = -1;
  357. Widget::TextureResType resType = Widget::TextureResType::LOCAL;
  358. if (tagAttrValueMap.find("src") != tagAttrValueMap.end()) {
  359. src = tagAttrValueMap.at("src").asString();
  360. }
  361. if (tagAttrValueMap.find("height") != tagAttrValueMap.end()) {
  362. height = tagAttrValueMap.at("height").asInt();
  363. }
  364. if (tagAttrValueMap.find("width") != tagAttrValueMap.end()) {
  365. width = tagAttrValueMap.at("width").asInt();
  366. }
  367. if (tagAttrValueMap.find("type") != tagAttrValueMap.end()) {
  368. // texture type
  369. // 0: normal file path
  370. // 1: sprite frame name
  371. int type = tagAttrValueMap.at("type").asInt();
  372. resType = type == 0 ? Widget::TextureResType::LOCAL : Widget::TextureResType::PLIST;
  373. }
  374. RichElementImage* elementImg = nullptr;
  375. if (src.length()) {
  376. elementImg = RichElementImage::create(0, Color3B::WHITE, 255, src, "", resType);
  377. if (0 <= height) elementImg->setHeight(height);
  378. if (0 <= width) elementImg->setWidth(width);
  379. }
  380. return make_pair(ValueMap(), elementImg);
  381. });
  382. MyXMLVisitor::setTagDescription("a", true, [](const ValueMap& tagAttrValueMap) {
  383. // supported attributes:
  384. ValueMap attrValueMap;
  385. if (tagAttrValueMap.find("href") != tagAttrValueMap.end()) {
  386. attrValueMap[RichText::KEY_URL] = tagAttrValueMap.at("href").asString();
  387. }
  388. return make_pair(attrValueMap, nullptr);
  389. });
  390. MyXMLVisitor::setTagDescription("br", false, [](const ValueMap& /*tagAttrValueMap*/) {
  391. RichElementNewLine* richElement = RichElementNewLine::create(0, Color3B::WHITE, 255);
  392. return make_pair(ValueMap(), richElement);
  393. });
  394. MyXMLVisitor::setTagDescription("outline", true, [](const ValueMap& tagAttrValueMap) {
  395. // supported attributes:
  396. // color, size
  397. ValueMap attrValueMap;
  398. attrValueMap[RichText::KEY_TEXT_STYLE] = RichText::VALUE_TEXT_STYLE_OUTLINE;
  399. if (tagAttrValueMap.find("color") != tagAttrValueMap.end()) {
  400. attrValueMap[RichText::KEY_TEXT_OUTLINE_COLOR] = tagAttrValueMap.at("color").asString();
  401. }
  402. if (tagAttrValueMap.find("size") != tagAttrValueMap.end()) {
  403. attrValueMap[RichText::KEY_TEXT_OUTLINE_SIZE] = tagAttrValueMap.at("size").asString();
  404. }
  405. return make_pair(attrValueMap, nullptr);
  406. });
  407. MyXMLVisitor::setTagDescription("shadow", true, [](const ValueMap& tagAttrValueMap) {
  408. // supported attributes:
  409. // color, offsetWidth, offsetHeight, blurRadius
  410. ValueMap attrValueMap;
  411. attrValueMap[RichText::KEY_TEXT_STYLE] = RichText::VALUE_TEXT_STYLE_SHADOW;
  412. if (tagAttrValueMap.find("color") != tagAttrValueMap.end()) {
  413. attrValueMap[RichText::KEY_TEXT_SHADOW_COLOR] = tagAttrValueMap.at("color").asString();
  414. }
  415. if (tagAttrValueMap.find("offsetWidth") != tagAttrValueMap.end()) {
  416. attrValueMap[RichText::KEY_TEXT_SHADOW_OFFSET_WIDTH] = tagAttrValueMap.at("offsetWidth").asString();
  417. }
  418. if (tagAttrValueMap.find("offsetHeight") != tagAttrValueMap.end()) {
  419. attrValueMap[RichText::KEY_TEXT_SHADOW_OFFSET_HEIGHT] = tagAttrValueMap.at("offsetHeight").asString();
  420. }
  421. if (tagAttrValueMap.find("blurRadius") != tagAttrValueMap.end()) {
  422. attrValueMap[RichText::KEY_TEXT_SHADOW_BLUR_RADIUS] = tagAttrValueMap.at("blurRadius").asString();
  423. }
  424. return make_pair(attrValueMap, nullptr);
  425. });
  426. MyXMLVisitor::setTagDescription("glow", true, [](const ValueMap& tagAttrValueMap) {
  427. // supported attributes:
  428. // color
  429. ValueMap attrValueMap;
  430. attrValueMap[RichText::KEY_TEXT_STYLE] = RichText::VALUE_TEXT_STYLE_GLOW;
  431. if (tagAttrValueMap.find("color") != tagAttrValueMap.end()) {
  432. attrValueMap[RichText::KEY_TEXT_GLOW_COLOR] = tagAttrValueMap.at("color").asString();
  433. }
  434. return make_pair(attrValueMap, nullptr);
  435. });
  436. }
  437. MyXMLVisitor::~MyXMLVisitor()
  438. {
  439. }
  440. Color3B MyXMLVisitor::getColor() const
  441. {
  442. for (auto i = _fontElements.rbegin(), iRend = _fontElements.rend(); i != iRend; ++i)
  443. {
  444. if (i->hasColor)
  445. return i->color;
  446. }
  447. return Color3B::WHITE;
  448. }
  449. float MyXMLVisitor::getFontSize() const
  450. {
  451. for (auto i = _fontElements.rbegin(), iRend = _fontElements.rend(); i != iRend; ++i)
  452. {
  453. if (i->fontSize != -1)
  454. return i->fontSize;
  455. }
  456. return 12;
  457. }
  458. std::string MyXMLVisitor::getFace() const
  459. {
  460. for (auto i = _fontElements.rbegin(), iRend = _fontElements.rend(); i != iRend; ++i)
  461. {
  462. if (i->face.size() != 0)
  463. return i->face;
  464. }
  465. return "fonts/Marker Felt.ttf";
  466. }
  467. std::string MyXMLVisitor::getURL() const
  468. {
  469. for (auto i = _fontElements.rbegin(), iRend = _fontElements.rend(); i != iRend; ++i)
  470. {
  471. if (i->url.size() != 0)
  472. return i->url;
  473. }
  474. return "";
  475. }
  476. bool MyXMLVisitor::getBold() const
  477. {
  478. for (auto i = _fontElements.rbegin(), iRend = _fontElements.rend(); i != iRend; ++i)
  479. {
  480. if (i->bold)
  481. return true;
  482. }
  483. return false;
  484. }
  485. bool MyXMLVisitor::getItalics() const
  486. {
  487. for (auto i = _fontElements.rbegin(), iRend = _fontElements.rend(); i != iRend; ++i)
  488. {
  489. if (i->italics)
  490. return true;
  491. }
  492. return false;
  493. }
  494. bool MyXMLVisitor::getUnderline() const
  495. {
  496. for (auto i = _fontElements.rbegin(), iRend = _fontElements.rend(); i != iRend; ++i)
  497. {
  498. if (i->line == StyleLine::UNDERLINE)
  499. return true;
  500. }
  501. return false;
  502. }
  503. bool MyXMLVisitor::getStrikethrough() const
  504. {
  505. for (auto i = _fontElements.rbegin(), iRend = _fontElements.rend(); i != iRend; ++i)
  506. {
  507. if (i->line == StyleLine::STRIKETHROUGH)
  508. return true;
  509. }
  510. return false;
  511. }
  512. std::tuple<bool, Color3B, int> MyXMLVisitor::getOutline() const
  513. {
  514. for (auto i = _fontElements.rbegin(), iRend = _fontElements.rend(); i != iRend; ++i)
  515. {
  516. if (i->effect == StyleEffect::OUTLINE)
  517. return std::make_tuple(true, i->outlineColor, i->outlineSize);
  518. }
  519. return std::make_tuple(false, Color3B::WHITE, -1);
  520. }
  521. std::tuple<bool, Color3B, cocos2d::Size, int> MyXMLVisitor::getShadow() const
  522. {
  523. for (auto i = _fontElements.rbegin(), iRend = _fontElements.rend(); i != iRend; ++i)
  524. {
  525. if (i->effect == StyleEffect::SHADOW)
  526. return std::make_tuple(true, i->shadowColor, i->shadowOffset, i->shadowBlurRadius);
  527. }
  528. return std::make_tuple(false, Color3B::BLACK, Size(2.0, -2.0), 0);
  529. }
  530. std::tuple<bool, Color3B> MyXMLVisitor::getGlow() const
  531. {
  532. for (auto i = _fontElements.rbegin(), iRend = _fontElements.rend(); i != iRend; ++i)
  533. {
  534. if (i->effect == StyleEffect::GLOW)
  535. return std::make_tuple(true, i->glowColor);
  536. }
  537. return std::make_tuple(false, Color3B::WHITE);
  538. }
  539. void MyXMLVisitor::startElement(void* /*ctx*/, const char *elementName, const char **atts)
  540. {
  541. auto it = _tagTables.find(elementName);
  542. if (it != _tagTables.end()) {
  543. auto tagBehavior = it->second;
  544. if (tagBehavior.handleVisitEnter != nullptr) {
  545. ValueMap&& tagAttrValueMap = tagAttrMapWithXMLElement(atts);
  546. auto result = tagBehavior.handleVisitEnter(tagAttrValueMap);
  547. ValueMap& attrValueMap = result.first;
  548. RichElement* richElement = result.second;
  549. if (!attrValueMap.empty()) {
  550. Attributes attributes;
  551. if (attrValueMap.find(RichText::KEY_FONT_SIZE) != attrValueMap.end()) {
  552. attributes.fontSize = attrValueMap.at(RichText::KEY_FONT_SIZE).asFloat();
  553. }
  554. if (attrValueMap.find(RichText::KEY_FONT_SMALL) != attrValueMap.end()) {
  555. attributes.fontSize = getFontSize() * 0.8f;
  556. }
  557. if (attrValueMap.find(RichText::KEY_FONT_BIG) != attrValueMap.end()) {
  558. attributes.fontSize = getFontSize() * 1.25f;
  559. }
  560. if (attrValueMap.find(RichText::KEY_FONT_COLOR_STRING) != attrValueMap.end()) {
  561. attributes.setColor(_richText->color3BWithString(attrValueMap.at(RichText::KEY_FONT_COLOR_STRING).asString()));
  562. }
  563. if (attrValueMap.find(RichText::KEY_FONT_FACE) != attrValueMap.end()) {
  564. attributes.face = attrValueMap.at(RichText::KEY_FONT_FACE).asString();
  565. }
  566. if (attrValueMap.find(RichText::KEY_TEXT_BOLD) != attrValueMap.end()) {
  567. attributes.bold = true;
  568. }
  569. if (attrValueMap.find(RichText::KEY_TEXT_ITALIC) != attrValueMap.end()) {
  570. attributes.italics = true;
  571. }
  572. if (attrValueMap.find(RichText::KEY_TEXT_LINE) != attrValueMap.end()) {
  573. auto keyTextLine = attrValueMap.at(RichText::KEY_TEXT_LINE).asString();
  574. if (keyTextLine == RichText::VALUE_TEXT_LINE_DEL) {
  575. attributes.line = StyleLine::STRIKETHROUGH;
  576. }
  577. else if (keyTextLine == RichText::VALUE_TEXT_LINE_UNDER) {
  578. attributes.line = StyleLine::UNDERLINE;
  579. }
  580. }
  581. if (attrValueMap.find(RichText::KEY_URL) != attrValueMap.end()) {
  582. attributes.url = attrValueMap.at(RichText::KEY_URL).asString();
  583. attributes.setColor(_richText->getAnchorFontColor3B());
  584. if (_richText->isAnchorTextBoldEnabled()) {
  585. attributes.bold = true;
  586. }
  587. if (_richText->isAnchorTextItalicEnabled()) {
  588. attributes.italics = true;
  589. }
  590. if (_richText->isAnchorTextUnderlineEnabled()) {
  591. attributes.line = StyleLine::UNDERLINE;
  592. }
  593. if (_richText->isAnchorTextDelEnabled()) {
  594. attributes.line = StyleLine::STRIKETHROUGH;
  595. }
  596. if (_richText->isAnchorTextOutlineEnabled()) {
  597. attributes.effect = StyleEffect::OUTLINE;
  598. attributes.outlineColor = _richText->getAnchorTextOutlineColor3B();
  599. attributes.outlineSize = _richText->getAnchorTextOutlineSize();
  600. }
  601. if (_richText->isAnchorTextShadowEnabled()) {
  602. attributes.effect = StyleEffect::SHADOW;
  603. attributes.shadowColor = _richText->getAnchorTextShadowColor3B();
  604. attributes.shadowOffset = _richText->getAnchorTextShadowOffset();
  605. attributes.shadowBlurRadius = _richText->getAnchorTextShadowBlurRadius();
  606. }
  607. if (_richText->isAnchorTextGlowEnabled()) {
  608. attributes.effect = StyleEffect::GLOW;
  609. attributes.glowColor = _richText->getAnchorTextGlowColor3B();
  610. }
  611. }
  612. if (attrValueMap.find(RichText::KEY_TEXT_STYLE) != attrValueMap.end()) {
  613. auto keyTextStyle = attrValueMap.at(RichText::KEY_TEXT_STYLE).asString();
  614. if (keyTextStyle == RichText::VALUE_TEXT_STYLE_OUTLINE) {
  615. attributes.effect = StyleEffect::OUTLINE;
  616. if (attrValueMap.find(RichText::KEY_TEXT_OUTLINE_COLOR) != attrValueMap.end()) {
  617. attributes.outlineColor = _richText->color3BWithString(attrValueMap.at(RichText::KEY_TEXT_OUTLINE_COLOR).asString());
  618. }
  619. if (attrValueMap.find(RichText::KEY_TEXT_OUTLINE_SIZE) != attrValueMap.end()) {
  620. attributes.outlineSize = attrValueMap.at(RichText::KEY_TEXT_OUTLINE_SIZE).asInt();
  621. }
  622. }
  623. else if (keyTextStyle == RichText::VALUE_TEXT_STYLE_SHADOW) {
  624. attributes.effect = StyleEffect::SHADOW;
  625. if (attrValueMap.find(RichText::KEY_TEXT_SHADOW_COLOR) != attrValueMap.end()) {
  626. attributes.shadowColor = _richText->color3BWithString(attrValueMap.at(RichText::KEY_TEXT_SHADOW_COLOR).asString());
  627. }
  628. if ((attrValueMap.find(RichText::KEY_TEXT_SHADOW_OFFSET_WIDTH) != attrValueMap.end())
  629. && (attrValueMap.find(RichText::KEY_TEXT_SHADOW_OFFSET_HEIGHT) != attrValueMap.end())) {
  630. attributes.shadowOffset = Size(attrValueMap.at(RichText::KEY_TEXT_SHADOW_OFFSET_WIDTH).asFloat(),
  631. attrValueMap.at(RichText::KEY_TEXT_SHADOW_OFFSET_HEIGHT).asFloat());
  632. }
  633. if (attrValueMap.find(RichText::KEY_TEXT_SHADOW_BLUR_RADIUS) != attrValueMap.end()) {
  634. attributes.shadowBlurRadius = attrValueMap.at(RichText::KEY_TEXT_SHADOW_BLUR_RADIUS).asInt();
  635. }
  636. }
  637. else if (keyTextStyle == RichText::VALUE_TEXT_STYLE_GLOW) {
  638. attributes.effect = StyleEffect::GLOW;
  639. if (attrValueMap.find(RichText::KEY_TEXT_GLOW_COLOR) != attrValueMap.end()) {
  640. attributes.glowColor = _richText->color3BWithString(attrValueMap.at(RichText::KEY_TEXT_GLOW_COLOR).asString());
  641. }
  642. }
  643. }
  644. pushBackFontElement(attributes);
  645. }
  646. if (richElement) {
  647. if (richElement->equalType(RichElement::Type::IMAGE)) {
  648. richElement->setColor(getColor());
  649. auto* richElementImage = static_cast<RichElementImage*>(richElement);
  650. richElementImage->setUrl(getURL());
  651. }
  652. else if (richElement->equalType(RichElement::Type::NEWLINE)) {
  653. richElement->setColor(getColor());
  654. }
  655. pushBackElement(richElement);
  656. }
  657. }
  658. }
  659. }
  660. void MyXMLVisitor::endElement(void* /*ctx*/, const char *elementName)
  661. {
  662. auto it = _tagTables.find(elementName);
  663. if (it != _tagTables.end()) {
  664. auto tagBehavior = it->second;
  665. if (tagBehavior.isFontElement) {
  666. popBackFontElement();
  667. }
  668. }
  669. }
  670. void MyXMLVisitor::textHandler(void* /*ctx*/, const char *str, size_t len)
  671. {
  672. std::string text(str, len);
  673. auto color = getColor();
  674. auto face = getFace();
  675. auto fontSize = getFontSize();
  676. auto italics = getItalics();
  677. auto underline = getUnderline();
  678. auto strikethrough = getStrikethrough();
  679. auto bold = getBold();
  680. auto url = getURL();
  681. auto outline = getOutline();
  682. auto shadow = getShadow();
  683. auto glow = getGlow();
  684. uint32_t flags = 0;
  685. if (italics)
  686. flags |= RichElementText::ITALICS_FLAG;
  687. if (bold)
  688. flags |= RichElementText::BOLD_FLAG;
  689. if (underline)
  690. flags |= RichElementText::UNDERLINE_FLAG;
  691. if (strikethrough)
  692. flags |= RichElementText::STRIKETHROUGH_FLAG;
  693. if (url.size() > 0)
  694. flags |= RichElementText::URL_FLAG;
  695. if (std::get<0>(outline))
  696. flags |= RichElementText::OUTLINE_FLAG;
  697. if (std::get<0>(shadow))
  698. flags |= RichElementText::SHADOW_FLAG;
  699. if (std::get<0>(glow))
  700. flags |= RichElementText::GLOW_FLAG;
  701. auto element = RichElementText::create(0, color, 255, text, face, fontSize, flags, url,
  702. std::get<1>(outline), std::get<2>(outline),
  703. std::get<1>(shadow), std::get<2>(shadow), std::get<3>(shadow),
  704. std::get<1>(glow));
  705. _richText->pushBackElement(element);
  706. }
  707. void MyXMLVisitor::pushBackFontElement(const MyXMLVisitor::Attributes& attribs)
  708. {
  709. _fontElements.push_back(attribs);
  710. }
  711. void MyXMLVisitor::popBackFontElement()
  712. {
  713. _fontElements.pop_back();
  714. }
  715. void MyXMLVisitor::pushBackElement(RichElement* element)
  716. {
  717. _richText->pushBackElement(element);
  718. }
  719. void MyXMLVisitor::setTagDescription(const std::string& tag, bool isFontElement, RichText::VisitEnterHandler handleVisitEnter)
  720. {
  721. MyXMLVisitor::_tagTables[tag] = {isFontElement, handleVisitEnter};
  722. }
  723. void MyXMLVisitor::removeTagDescription(const std::string& tag)
  724. {
  725. MyXMLVisitor::_tagTables.erase(tag);
  726. }
  727. ValueMap MyXMLVisitor::tagAttrMapWithXMLElement(const char ** attrs)
  728. {
  729. ValueMap tagAttrValueMap;
  730. for (const char** attr = attrs; *attr != nullptr; attr = (attrs += 2)) {
  731. if (attr[0] && attr[1]) {
  732. tagAttrValueMap[attr[0]] = attr[1];
  733. }
  734. }
  735. return tagAttrValueMap;
  736. }
  737. const std::string RichText::KEY_VERTICAL_SPACE("KEY_VERTICAL_SPACE");
  738. const std::string RichText::KEY_WRAP_MODE("KEY_WRAP_MODE");
  739. const std::string RichText::KEY_HORIZONTAL_ALIGNMENT("KEY_HORIZONTAL_ALIGNMENT");
  740. const std::string RichText::KEY_FONT_COLOR_STRING("KEY_FONT_COLOR_STRING");
  741. const std::string RichText::KEY_FONT_SIZE("KEY_FONT_SIZE");
  742. const std::string RichText::KEY_FONT_SMALL("KEY_FONT_SMALL");
  743. const std::string RichText::KEY_FONT_BIG("KEY_FONT_BIG");
  744. const std::string RichText::KEY_FONT_FACE("KEY_FONT_FACE");
  745. const std::string RichText::KEY_TEXT_BOLD("KEY_TEXT_BOLD");
  746. const std::string RichText::KEY_TEXT_ITALIC("KEY_TEXT_ITALIC");
  747. const std::string RichText::KEY_TEXT_LINE("KEY_TEXT_LINE");
  748. const std::string RichText::VALUE_TEXT_LINE_NONE("VALUE_TEXT_LINE_NONE");
  749. const std::string RichText::VALUE_TEXT_LINE_DEL("VALUE_TEXT_LINE_DEL");
  750. const std::string RichText::VALUE_TEXT_LINE_UNDER("VALUE_TEXT_LINE_UNDER");
  751. const std::string RichText::KEY_TEXT_STYLE("KEY_TEXT_STYLE");
  752. const std::string RichText::VALUE_TEXT_STYLE_NONE("VALUE_TEXT_STYLE_NONE");
  753. const std::string RichText::VALUE_TEXT_STYLE_OUTLINE("VALUE_TEXT_STYLE_OUTLINE");
  754. const std::string RichText::VALUE_TEXT_STYLE_SHADOW("VALUE_TEXT_STYLE_SHADOW");
  755. const std::string RichText::VALUE_TEXT_STYLE_GLOW("VALUE_TEXT_STYLE_GLOW");
  756. const std::string RichText::KEY_TEXT_OUTLINE_COLOR("KEY_TEXT_OUTLINE_COLOR");
  757. const std::string RichText::KEY_TEXT_OUTLINE_SIZE("KEY_TEXT_OUTLINE_SIZE");
  758. const std::string RichText::KEY_TEXT_SHADOW_COLOR("KEY_TEXT_SHADOW_COLOR");
  759. const std::string RichText::KEY_TEXT_SHADOW_OFFSET_WIDTH("KEY_TEXT_SHADOW_OFFSET_WIDTH");
  760. const std::string RichText::KEY_TEXT_SHADOW_OFFSET_HEIGHT("KEY_TEXT_SHADOW_OFFSET_HEIGHT");
  761. const std::string RichText::KEY_TEXT_SHADOW_BLUR_RADIUS("KEY_TEXT_SHADOW_BLUR_RADIUS");
  762. const std::string RichText::KEY_TEXT_GLOW_COLOR("KEY_TEXT_GLOW_COLOR");
  763. const std::string RichText::KEY_URL("KEY_URL");
  764. const std::string RichText::KEY_ANCHOR_FONT_COLOR_STRING("KEY_ANCHOR_FONT_COLOR_STRING");
  765. const std::string RichText::KEY_ANCHOR_TEXT_BOLD("KEY_ANCHOR_TEXT_BOLD");
  766. const std::string RichText::KEY_ANCHOR_TEXT_ITALIC("KEY_ANCHOR_TEXT_ITALIC");
  767. const std::string RichText::KEY_ANCHOR_TEXT_LINE("KEY_ANCHOR_TEXT_LINE");
  768. const std::string RichText::KEY_ANCHOR_TEXT_STYLE("KEY_ANCHOR_TEXT_STYLE");
  769. const std::string RichText::KEY_ANCHOR_TEXT_OUTLINE_COLOR("KEY_ANCHOR_TEXT_OUTLINE_COLOR");
  770. const std::string RichText::KEY_ANCHOR_TEXT_OUTLINE_SIZE("KEY_ANCHOR_TEXT_OUTLINE_SIZE");
  771. const std::string RichText::KEY_ANCHOR_TEXT_SHADOW_COLOR("KEY_ANCHOR_TEXT_SHADOW_COLOR");
  772. const std::string RichText::KEY_ANCHOR_TEXT_SHADOW_OFFSET_WIDTH("KEY_ANCHOR_TEXT_SHADOW_OFFSET_WIDTH");
  773. const std::string RichText::KEY_ANCHOR_TEXT_SHADOW_OFFSET_HEIGHT("KEY_ANCHOR_TEXT_SHADOW_OFFSET_HEIGHT");
  774. const std::string RichText::KEY_ANCHOR_TEXT_SHADOW_BLUR_RADIUS("KEY_ANCHOR_TEXT_SHADOW_BLUR_RADIUS");
  775. const std::string RichText::KEY_ANCHOR_TEXT_GLOW_COLOR("KEY_ANCHOR_TEXT_GLOW_COLOR");
  776. RichText::RichText()
  777. : _formatTextDirty(true)
  778. , _leftSpaceWidth(0.0f)
  779. {
  780. _defaults[KEY_VERTICAL_SPACE] = 0.0f;
  781. _defaults[KEY_WRAP_MODE] = static_cast<int>(WrapMode::WRAP_PER_WORD);
  782. _defaults[KEY_HORIZONTAL_ALIGNMENT] = static_cast<int>(HorizontalAlignment::LEFT);
  783. _defaults[KEY_FONT_COLOR_STRING] = "#ffffff";
  784. _defaults[KEY_FONT_SIZE] = 12.0f;
  785. _defaults[KEY_FONT_FACE] = "Verdana";
  786. _defaults[KEY_ANCHOR_FONT_COLOR_STRING] = "#0000FF";
  787. _defaults[KEY_ANCHOR_TEXT_BOLD] = false;
  788. _defaults[KEY_ANCHOR_TEXT_ITALIC] = false;
  789. _defaults[KEY_ANCHOR_TEXT_LINE] = VALUE_TEXT_LINE_NONE;
  790. _defaults[KEY_ANCHOR_TEXT_STYLE] = VALUE_TEXT_STYLE_NONE;
  791. }
  792. RichText::~RichText()
  793. {
  794. _richElements.clear();
  795. }
  796. RichText* RichText::create()
  797. {
  798. RichText* widget = new (std::nothrow) RichText();
  799. if (widget && widget->init())
  800. {
  801. widget->autorelease();
  802. return widget;
  803. }
  804. CC_SAFE_DELETE(widget);
  805. return nullptr;
  806. }
  807. RichText* RichText::createWithXML(const std::string& xml, const ValueMap& defaults, const OpenUrlHandler& handleOpenUrl)
  808. {
  809. RichText* widget = new (std::nothrow) RichText();
  810. if (widget && widget->initWithXML(xml, defaults, handleOpenUrl))
  811. {
  812. widget->autorelease();
  813. return widget;
  814. }
  815. CC_SAFE_DELETE(widget);
  816. return nullptr;
  817. }
  818. bool RichText::init()
  819. {
  820. if (Widget::init())
  821. {
  822. return true;
  823. }
  824. return false;
  825. }
  826. bool RichText::initWithXML(const std::string& origxml, const ValueMap& defaults, const OpenUrlHandler& handleOpenUrl)
  827. {
  828. static std::function<std::string(RichText*)> startTagFont = [](RichText* richText) {
  829. std::string fontFace = richText->getFontFace();
  830. std::stringstream ss;
  831. ss << richText->getFontSize();
  832. std::string fontSize = ss.str();
  833. std::string fontColor = richText->getFontColor();
  834. return "<font face=\"" + fontFace + "\" size=\"" + fontSize + "\" color=\"" + fontColor + "\">";
  835. };
  836. if (Widget::init())
  837. {
  838. setDefaults(defaults);
  839. setOpenUrlHandler(handleOpenUrl);
  840. // solves to issues:
  841. // - creates defaults values
  842. // - makes sure that the xml well formed and starts with an element
  843. std::string xml = startTagFont(this);
  844. xml += origxml;
  845. xml += "</font>";
  846. MyXMLVisitor visitor(this);
  847. SAXParser parser;
  848. parser.setDelegator(&visitor);
  849. return parser.parseIntrusive(&xml.front(), xml.length());
  850. }
  851. return false;
  852. }
  853. void RichText::initRenderer()
  854. {
  855. }
  856. void RichText::insertElement(RichElement *element, int index)
  857. {
  858. _richElements.insert(index, element);
  859. _formatTextDirty = true;
  860. }
  861. void RichText::pushBackElement(RichElement *element)
  862. {
  863. _richElements.pushBack(element);
  864. _formatTextDirty = true;
  865. }
  866. void RichText::removeElement(int index)
  867. {
  868. _richElements.erase(index);
  869. _formatTextDirty = true;
  870. }
  871. void RichText::removeElement(RichElement *element)
  872. {
  873. _richElements.eraseObject(element);
  874. _formatTextDirty = true;
  875. }
  876. RichText::WrapMode RichText::getWrapMode() const
  877. {
  878. return static_cast<RichText::WrapMode>(_defaults.at(KEY_WRAP_MODE).asInt());
  879. }
  880. void RichText::setWrapMode(RichText::WrapMode wrapMode)
  881. {
  882. if (static_cast<RichText::WrapMode>(_defaults.at(KEY_WRAP_MODE).asInt()) != wrapMode)
  883. {
  884. _defaults[KEY_WRAP_MODE] = static_cast<int>(wrapMode);
  885. _formatTextDirty = true;
  886. }
  887. }
  888. RichText::HorizontalAlignment RichText::getHorizontalAlignment() const
  889. {
  890. return static_cast<RichText::HorizontalAlignment>(_defaults.at(KEY_HORIZONTAL_ALIGNMENT).asInt());
  891. }
  892. void RichText::setHorizontalAlignment(cocos2d::ui::RichText::HorizontalAlignment a)
  893. {
  894. if (static_cast<RichText::HorizontalAlignment>(_defaults.at(KEY_HORIZONTAL_ALIGNMENT).asInt()) != a)
  895. {
  896. _defaults[KEY_HORIZONTAL_ALIGNMENT] = static_cast<int>(a);
  897. _formatTextDirty = true;
  898. }
  899. }
  900. void RichText::setFontColor(const std::string& color)
  901. {
  902. _defaults[KEY_FONT_COLOR_STRING] = color;
  903. }
  904. std::string RichText::getFontColor()
  905. {
  906. return _defaults.at(KEY_FONT_COLOR_STRING).asString();
  907. }
  908. cocos2d::Color3B RichText::getFontColor3B()
  909. {
  910. return color3BWithString(getFontColor());
  911. }
  912. void RichText::setFontSize(float size)
  913. {
  914. _defaults[KEY_FONT_SIZE] = size;
  915. }
  916. float RichText::getFontSize()
  917. {
  918. return _defaults.at(KEY_FONT_SIZE).asFloat();
  919. }
  920. void RichText::setFontFace(const std::string& face)
  921. {
  922. _defaults[KEY_FONT_FACE] = face;
  923. }
  924. std::string RichText::getFontFace()
  925. {
  926. return _defaults.at(KEY_FONT_FACE).asString();
  927. }
  928. void RichText::setAnchorFontColor(const std::string& color)
  929. {
  930. _defaults[KEY_ANCHOR_FONT_COLOR_STRING] = color;
  931. }
  932. std::string RichText::getAnchorFontColor()
  933. {
  934. return _defaults.at(KEY_ANCHOR_FONT_COLOR_STRING).asString();
  935. }
  936. cocos2d::Color3B RichText::getAnchorFontColor3B()
  937. {
  938. return color3BWithString(getAnchorFontColor());
  939. }
  940. void RichText::setAnchorTextBold(bool enable)
  941. {
  942. _defaults[KEY_ANCHOR_TEXT_BOLD] = enable;
  943. }
  944. bool RichText::isAnchorTextBoldEnabled()
  945. {
  946. return _defaults[KEY_ANCHOR_TEXT_BOLD].asBool();
  947. }
  948. void RichText::setAnchorTextItalic(bool enable)
  949. {
  950. _defaults[KEY_ANCHOR_TEXT_ITALIC] = enable;
  951. }
  952. bool RichText::isAnchorTextItalicEnabled()
  953. {
  954. return _defaults[KEY_ANCHOR_TEXT_ITALIC].asBool();
  955. }
  956. void RichText::setAnchorTextDel(bool enable)
  957. {
  958. if (enable)
  959. _defaults[KEY_ANCHOR_TEXT_LINE] = VALUE_TEXT_LINE_DEL;
  960. else if (_defaults[KEY_ANCHOR_TEXT_LINE].asString() == VALUE_TEXT_LINE_DEL)
  961. _defaults[KEY_ANCHOR_TEXT_LINE] = VALUE_TEXT_LINE_NONE;
  962. }
  963. bool RichText::isAnchorTextDelEnabled()
  964. {
  965. return (_defaults[KEY_ANCHOR_TEXT_LINE].asString() == VALUE_TEXT_LINE_DEL);
  966. }
  967. void RichText::setAnchorTextUnderline(bool enable)
  968. {
  969. if (enable)
  970. _defaults[KEY_ANCHOR_TEXT_LINE] = VALUE_TEXT_LINE_UNDER;
  971. else if (_defaults[KEY_ANCHOR_TEXT_LINE].asString() == VALUE_TEXT_LINE_UNDER)
  972. _defaults[KEY_ANCHOR_TEXT_LINE] = VALUE_TEXT_LINE_NONE;
  973. }
  974. bool RichText::isAnchorTextUnderlineEnabled()
  975. {
  976. return (_defaults[KEY_ANCHOR_TEXT_LINE].asString() == VALUE_TEXT_LINE_UNDER);
  977. }
  978. void RichText::setAnchorTextOutline(bool enable, const Color3B& outlineColor, int outlineSize)
  979. {
  980. if (enable)
  981. _defaults[KEY_ANCHOR_TEXT_STYLE] = VALUE_TEXT_STYLE_OUTLINE;
  982. else if (_defaults[KEY_ANCHOR_TEXT_STYLE].asString() == VALUE_TEXT_STYLE_OUTLINE)
  983. _defaults[KEY_ANCHOR_TEXT_STYLE] = VALUE_TEXT_STYLE_NONE;
  984. _defaults[KEY_ANCHOR_TEXT_OUTLINE_COLOR] = stringWithColor3B(outlineColor);
  985. _defaults[KEY_ANCHOR_TEXT_OUTLINE_SIZE] = outlineSize;
  986. }
  987. bool RichText::isAnchorTextOutlineEnabled()
  988. {
  989. return (_defaults[KEY_ANCHOR_TEXT_STYLE].asString() == VALUE_TEXT_STYLE_OUTLINE);
  990. }
  991. Color3B RichText::getAnchorTextOutlineColor3B()
  992. {
  993. if (_defaults.find(KEY_ANCHOR_TEXT_OUTLINE_COLOR) != _defaults.end()) {
  994. return color3BWithString(_defaults.at(KEY_ANCHOR_TEXT_OUTLINE_COLOR).asString());
  995. }
  996. return Color3B();
  997. }
  998. int RichText::getAnchorTextOutlineSize()
  999. {
  1000. if (_defaults.find(KEY_ANCHOR_TEXT_OUTLINE_SIZE) != _defaults.end()) {
  1001. return _defaults.at(KEY_ANCHOR_TEXT_OUTLINE_SIZE).asInt();
  1002. }
  1003. return -1;
  1004. }
  1005. void RichText::setAnchorTextShadow(bool enable, const Color3B& shadowColor, const Size& offset, int blurRadius)
  1006. {
  1007. if (enable)
  1008. _defaults[KEY_ANCHOR_TEXT_STYLE] = VALUE_TEXT_STYLE_SHADOW;
  1009. else if (_defaults[KEY_ANCHOR_TEXT_STYLE].asString() == VALUE_TEXT_STYLE_SHADOW)
  1010. _defaults[KEY_ANCHOR_TEXT_STYLE] = VALUE_TEXT_STYLE_NONE;
  1011. _defaults[KEY_ANCHOR_TEXT_SHADOW_COLOR] = stringWithColor3B(shadowColor);
  1012. _defaults[KEY_ANCHOR_TEXT_SHADOW_OFFSET_WIDTH] = offset.width;
  1013. _defaults[KEY_ANCHOR_TEXT_SHADOW_OFFSET_HEIGHT] = offset.height;
  1014. _defaults[KEY_ANCHOR_TEXT_SHADOW_BLUR_RADIUS] = blurRadius;
  1015. }
  1016. bool RichText::isAnchorTextShadowEnabled()
  1017. {
  1018. return (_defaults[KEY_ANCHOR_TEXT_STYLE].asString() == VALUE_TEXT_STYLE_SHADOW);
  1019. }
  1020. Color3B RichText::getAnchorTextShadowColor3B()
  1021. {
  1022. if (_defaults.find(KEY_ANCHOR_TEXT_SHADOW_COLOR) != _defaults.end()) {
  1023. return color3BWithString(_defaults.at(KEY_ANCHOR_TEXT_SHADOW_COLOR).asString());
  1024. }
  1025. return Color3B();
  1026. }
  1027. Size RichText::getAnchorTextShadowOffset()
  1028. {
  1029. float width = 2.0f;
  1030. float height = -2.0f;
  1031. if (_defaults.find(KEY_ANCHOR_TEXT_SHADOW_OFFSET_WIDTH) != _defaults.end()) {
  1032. width = _defaults.at(KEY_ANCHOR_TEXT_SHADOW_OFFSET_WIDTH).asFloat();
  1033. }
  1034. if (_defaults.find(KEY_ANCHOR_TEXT_SHADOW_OFFSET_HEIGHT) != _defaults.end()) {
  1035. height = _defaults.at(KEY_ANCHOR_TEXT_SHADOW_OFFSET_HEIGHT).asFloat();
  1036. }
  1037. return Size(width, height);
  1038. }
  1039. int RichText::getAnchorTextShadowBlurRadius()
  1040. {
  1041. if (_defaults.find(KEY_ANCHOR_TEXT_SHADOW_BLUR_RADIUS) != _defaults.end()) {
  1042. return _defaults.at(KEY_ANCHOR_TEXT_SHADOW_BLUR_RADIUS).asInt();
  1043. }
  1044. return 0;
  1045. }
  1046. void RichText::setAnchorTextGlow(bool enable, const Color3B& glowColor)
  1047. {
  1048. if (enable)
  1049. _defaults[KEY_ANCHOR_TEXT_STYLE] = VALUE_TEXT_STYLE_GLOW;
  1050. else if (_defaults[KEY_ANCHOR_TEXT_STYLE].asString() == VALUE_TEXT_STYLE_GLOW)
  1051. _defaults[KEY_ANCHOR_TEXT_STYLE] = VALUE_TEXT_STYLE_NONE;
  1052. _defaults[KEY_ANCHOR_TEXT_GLOW_COLOR] = stringWithColor3B(glowColor);
  1053. }
  1054. bool RichText::isAnchorTextGlowEnabled()
  1055. {
  1056. return (_defaults[KEY_ANCHOR_TEXT_STYLE].asString() == VALUE_TEXT_STYLE_GLOW);
  1057. }
  1058. Color3B RichText::getAnchorTextGlowColor3B()
  1059. {
  1060. if (_defaults.find(KEY_ANCHOR_TEXT_GLOW_COLOR) != _defaults.end()) {
  1061. return color3BWithString(_defaults.at(KEY_ANCHOR_TEXT_GLOW_COLOR).asString());
  1062. }
  1063. return Color3B();
  1064. }
  1065. void RichText::setDefaults(const ValueMap& defaults)
  1066. {
  1067. if (defaults.find(KEY_VERTICAL_SPACE) != defaults.end()) {
  1068. _defaults[KEY_VERTICAL_SPACE] = defaults.at(KEY_VERTICAL_SPACE).asFloat();
  1069. }
  1070. if (defaults.find(KEY_WRAP_MODE) != defaults.end()) {
  1071. _defaults[KEY_WRAP_MODE] = defaults.at(KEY_WRAP_MODE).asInt();
  1072. }
  1073. if (defaults.find(KEY_HORIZONTAL_ALIGNMENT) != defaults.end()) {
  1074. _defaults[KEY_HORIZONTAL_ALIGNMENT] = defaults.at(KEY_HORIZONTAL_ALIGNMENT).asInt();
  1075. }
  1076. if (defaults.find(KEY_FONT_COLOR_STRING) != defaults.end()) {
  1077. _defaults[KEY_FONT_COLOR_STRING] = defaults.at(KEY_FONT_COLOR_STRING).asString();
  1078. }
  1079. if (defaults.find(KEY_FONT_SIZE) != defaults.end()) {
  1080. _defaults[KEY_FONT_SIZE] = defaults.at(KEY_FONT_SIZE).asFloat();
  1081. }
  1082. if (defaults.find(KEY_FONT_FACE) != defaults.end()) {
  1083. _defaults[KEY_FONT_FACE] = defaults.at(KEY_FONT_FACE).asString();
  1084. }
  1085. if (defaults.find(KEY_ANCHOR_FONT_COLOR_STRING) != defaults.end()) {
  1086. _defaults[KEY_ANCHOR_FONT_COLOR_STRING] = defaults.at(KEY_ANCHOR_FONT_COLOR_STRING).asString();
  1087. }
  1088. if (defaults.find(KEY_ANCHOR_TEXT_BOLD) != defaults.end()) {
  1089. _defaults[KEY_ANCHOR_TEXT_BOLD] = defaults.at(KEY_ANCHOR_TEXT_BOLD).asBool();
  1090. }
  1091. if (defaults.find(KEY_ANCHOR_TEXT_ITALIC) != defaults.end()) {
  1092. _defaults[KEY_ANCHOR_TEXT_ITALIC] = defaults.at(KEY_ANCHOR_TEXT_ITALIC).asBool();
  1093. }
  1094. if (defaults.find(KEY_ANCHOR_TEXT_LINE) != defaults.end()) {
  1095. _defaults[KEY_ANCHOR_TEXT_LINE] = defaults.at(KEY_ANCHOR_TEXT_LINE).asString();
  1096. }
  1097. if (defaults.find(KEY_ANCHOR_TEXT_STYLE) != defaults.end()) {
  1098. _defaults[KEY_ANCHOR_TEXT_STYLE] = defaults.at(KEY_ANCHOR_TEXT_STYLE).asString();
  1099. }
  1100. if (defaults.find(KEY_ANCHOR_TEXT_OUTLINE_COLOR) != defaults.end()) {
  1101. _defaults[KEY_ANCHOR_TEXT_OUTLINE_COLOR] = defaults.at(KEY_ANCHOR_TEXT_OUTLINE_COLOR).asString();
  1102. }
  1103. if (defaults.find(KEY_ANCHOR_TEXT_OUTLINE_SIZE) != defaults.end()) {
  1104. _defaults[KEY_ANCHOR_TEXT_OUTLINE_SIZE] = defaults.at(KEY_ANCHOR_TEXT_OUTLINE_SIZE).asInt();
  1105. }
  1106. if (defaults.find(KEY_ANCHOR_TEXT_SHADOW_COLOR) != defaults.end()) {
  1107. _defaults[KEY_ANCHOR_TEXT_SHADOW_COLOR] = defaults.at(KEY_ANCHOR_TEXT_SHADOW_COLOR).asString();
  1108. }
  1109. if (defaults.find(KEY_ANCHOR_TEXT_SHADOW_OFFSET_WIDTH) != defaults.end()) {
  1110. _defaults[KEY_ANCHOR_TEXT_SHADOW_OFFSET_WIDTH] = defaults.at(KEY_ANCHOR_TEXT_SHADOW_OFFSET_WIDTH).asFloat();
  1111. }
  1112. if (defaults.find(KEY_ANCHOR_TEXT_SHADOW_OFFSET_HEIGHT) != defaults.end()) {
  1113. _defaults[KEY_ANCHOR_TEXT_SHADOW_OFFSET_HEIGHT] = defaults.at(KEY_ANCHOR_TEXT_SHADOW_OFFSET_HEIGHT).asFloat();
  1114. }
  1115. if (defaults.find(KEY_ANCHOR_TEXT_SHADOW_BLUR_RADIUS) != defaults.end()) {
  1116. _defaults[KEY_ANCHOR_TEXT_SHADOW_BLUR_RADIUS] = defaults.at(KEY_ANCHOR_TEXT_SHADOW_BLUR_RADIUS).asInt();
  1117. }
  1118. if (defaults.find(KEY_ANCHOR_TEXT_GLOW_COLOR) != defaults.end()) {
  1119. _defaults[KEY_ANCHOR_TEXT_GLOW_COLOR] = defaults.at(KEY_ANCHOR_TEXT_GLOW_COLOR).asString();
  1120. }
  1121. }
  1122. ValueMap RichText::getDefaults() const
  1123. {
  1124. ValueMap defaults;
  1125. return defaults;
  1126. }
  1127. cocos2d::Color3B RichText::color3BWithString(const std::string& color)
  1128. {
  1129. if (color.length() == 4) {
  1130. int r, g, b;
  1131. sscanf(color.c_str(), "%*c%1x%1x%1x", &r, &g, &b);
  1132. r += r * 16;
  1133. g += g * 16;
  1134. b += b * 16;
  1135. return Color3B(r, g, b);
  1136. }
  1137. else if (color.length() == 7) {
  1138. int r, g, b;
  1139. sscanf(color.c_str(), "%*c%2x%2x%2x", &r, &g, &b);
  1140. return Color3B(r, g, b);
  1141. }
  1142. else if (color.length() == 9) {
  1143. int r, g, b, a;
  1144. sscanf(color.c_str(), "%*c%2x%2x%2x%2x", &r, &g, &b, &a);
  1145. return Color3B(r, g, b);
  1146. }
  1147. return Color3B::WHITE;
  1148. }
  1149. std::string RichText::stringWithColor3B(const cocos2d::Color3B& color3b)
  1150. {
  1151. int r = color3b.r;
  1152. int g = color3b.g;
  1153. int b = color3b.b;
  1154. char buf[8];
  1155. snprintf(buf, sizeof(buf), "#%02x%02x%02x", r, g, b);
  1156. return std::string(buf, 7);
  1157. }
  1158. std::string RichText::stringWithColor4B(const cocos2d::Color4B& color4b)
  1159. {
  1160. int r = color4b.r;
  1161. int g = color4b.g;
  1162. int b = color4b.b;
  1163. int a = color4b.a;
  1164. char buf[10];
  1165. snprintf(buf, sizeof(buf), "#%02x%02x%02x%02x", r, g, b, a);
  1166. return std::string(buf, 9);
  1167. }
  1168. void RichText::setTagDescription(const std::string& tag, bool isFontElement, VisitEnterHandler handleVisitEnter)
  1169. {
  1170. MyXMLVisitor::setTagDescription(tag, isFontElement, handleVisitEnter);
  1171. }
  1172. void RichText::removeTagDescription(const std::string& tag)
  1173. {
  1174. MyXMLVisitor::removeTagDescription(tag);
  1175. }
  1176. void RichText::openUrl(const std::string& url)
  1177. {
  1178. if (_handleOpenUrl) {
  1179. _handleOpenUrl(url);
  1180. }
  1181. else {
  1182. Application::getInstance()->openURL(url);
  1183. }
  1184. }
  1185. void RichText::setOpenUrlHandler(const OpenUrlHandler& handleOpenUrl)
  1186. {
  1187. _handleOpenUrl = handleOpenUrl;
  1188. }
  1189. void RichText::formatText()
  1190. {
  1191. if (_formatTextDirty)
  1192. {
  1193. this->removeAllProtectedChildren();
  1194. _elementRenders.clear();
  1195. _lineHeights.clear();
  1196. if (_ignoreSize)
  1197. {
  1198. addNewLine();
  1199. for (ssize_t i=0, size = _richElements.size(); i<size; ++i)
  1200. {
  1201. RichElement* element = _richElements.at(i);
  1202. Node* elementRenderer = nullptr;
  1203. switch (element->_type)
  1204. {
  1205. case RichElement::Type::TEXT:
  1206. {
  1207. RichElementText* elmtText = static_cast<RichElementText*>(element);
  1208. Label* label;
  1209. if (FileUtils::getInstance()->isFileExist(elmtText->_fontName))
  1210. {
  1211. label = Label::createWithTTF(elmtText->_text, elmtText->_fontName, elmtText->_fontSize);
  1212. if (elmtText->_isNewLine) {
  1213. label->setLineBreakWithoutSpace(elmtText->_isNewLine);
  1214. }
  1215. if (elmtText->_maxNewLine) {
  1216. label->setMaxLineWidth(elmtText->_maxNewLine);
  1217. }
  1218. }
  1219. else
  1220. {
  1221. label = Label::createWithSystemFont(elmtText->_text, elmtText->_fontName, elmtText->_fontSize);
  1222. }
  1223. if (elmtText->_flags & RichElementText::ITALICS_FLAG)
  1224. label->enableItalics();
  1225. if (elmtText->_flags & RichElementText::BOLD_FLAG)
  1226. label->enableBold();
  1227. if (elmtText->_flags & RichElementText::UNDERLINE_FLAG)
  1228. label->enableUnderline();
  1229. if (elmtText->_flags & RichElementText::STRIKETHROUGH_FLAG)
  1230. label->enableStrikethrough();
  1231. if (elmtText->_flags & RichElementText::URL_FLAG)
  1232. label->addComponent(ListenerComponent::create(label, elmtText->_url,
  1233. std::bind(&RichText::openUrl, this, std::placeholders::_1)));
  1234. if (elmtText->_flags & RichElementText::OUTLINE_FLAG) {
  1235. label->enableOutline(Color4B(elmtText->_outlineColor), elmtText->_outlineSize);
  1236. }
  1237. if (elmtText->_flags & RichElementText::SHADOW_FLAG) {
  1238. label->enableShadow(Color4B(elmtText->_shadowColor),
  1239. elmtText->_shadowOffset,
  1240. elmtText->_shadowBlurRadius);
  1241. }
  1242. if (elmtText->_flags & RichElementText::GLOW_FLAG) {
  1243. label->enableGlow(Color4B(elmtText->_glowColor));
  1244. }
  1245. elementRenderer = label;
  1246. break;
  1247. }
  1248. case RichElement::Type::IMAGE:
  1249. {
  1250. RichElementImage* elmtImage = static_cast<RichElementImage*>(element);
  1251. if (elmtImage->_textureType == Widget::TextureResType::LOCAL)
  1252. elementRenderer = Sprite::create(elmtImage->_filePath);
  1253. else
  1254. elementRenderer = Sprite::createWithSpriteFrameName(elmtImage->_filePath);
  1255. if (elementRenderer && (elmtImage->_height != -1 || elmtImage->_width != -1))
  1256. {
  1257. auto currentSize = elementRenderer->getContentSize();
  1258. if (elmtImage->_width != -1)
  1259. elementRenderer->setScaleX(elmtImage->_width / currentSize.width);
  1260. if (elmtImage->_height != -1)
  1261. elementRenderer->setScaleY(elmtImage->_height / currentSize.height);
  1262. elementRenderer->setContentSize(Size(currentSize.width * elementRenderer->getScaleX(),
  1263. currentSize.height * elementRenderer->getScaleY()));
  1264. elementRenderer->addComponent(ListenerComponent::create(elementRenderer,
  1265. elmtImage->_url,
  1266. std::bind(&RichText::openUrl, this, std::placeholders::_1)));
  1267. }
  1268. break;
  1269. }
  1270. case RichElement::Type::CUSTOM:
  1271. {
  1272. RichElementCustomNode* elmtCustom = static_cast<RichElementCustomNode*>(element);
  1273. elementRenderer = elmtCustom->_customNode;
  1274. break;
  1275. }
  1276. case RichElement::Type::NEWLINE:
  1277. {
  1278. addNewLine();
  1279. break;
  1280. }
  1281. default:
  1282. break;
  1283. }
  1284. if (elementRenderer)
  1285. {
  1286. elementRenderer->setColor(element->_color);
  1287. elementRenderer->setOpacity(element->_opacity);
  1288. pushToContainer(elementRenderer);
  1289. }
  1290. }
  1291. }
  1292. else
  1293. {
  1294. addNewLine();
  1295. for (ssize_t i=0, size = _richElements.size(); i<size; ++i)
  1296. {
  1297. RichElement* element = static_cast<RichElement*>(_richElements.at(i));
  1298. switch (element->_type)
  1299. {
  1300. case RichElement::Type::TEXT:
  1301. {
  1302. RichElementText* elmtText = static_cast<RichElementText*>(element);
  1303. handleTextRenderer(elmtText->_text, elmtText->_fontName, elmtText->_fontSize, elmtText->_color,
  1304. elmtText->_opacity, elmtText->_flags, elmtText->_url,
  1305. elmtText->_outlineColor, elmtText->_outlineSize,
  1306. elmtText->_shadowColor, elmtText->_shadowOffset, elmtText->_shadowBlurRadius,
  1307. elmtText->_glowColor);
  1308. break;
  1309. }
  1310. case RichElement::Type::IMAGE:
  1311. {
  1312. RichElementImage* elmtImage = static_cast<RichElementImage*>(element);
  1313. handleImageRenderer(elmtImage->_filePath, elmtImage->_color, elmtImage->_opacity, elmtImage->_width, elmtImage->_height, elmtImage->_url);
  1314. break;
  1315. }
  1316. case RichElement::Type::CUSTOM:
  1317. {
  1318. RichElementCustomNode* elmtCustom = static_cast<RichElementCustomNode*>(element);
  1319. handleCustomRenderer(elmtCustom->_customNode);
  1320. break;
  1321. }
  1322. case RichElement::Type::NEWLINE:
  1323. {
  1324. addNewLine();
  1325. break;
  1326. }
  1327. default:
  1328. break;
  1329. }
  1330. }
  1331. }
  1332. formatRenderers();
  1333. _formatTextDirty = false;
  1334. }
  1335. }
  1336. namespace {
  1337. inline bool isUTF8CharWrappable(const StringUtils::StringUTF8::CharUTF8& ch)
  1338. {
  1339. return (!ch.isASCII() || !std::isalnum(ch._char[0], std::locale()));
  1340. }
  1341. int getPrevWordPos(const StringUtils::StringUTF8& text, int idx)
  1342. {
  1343. if (idx <= 0)
  1344. return -1;
  1345. // start from idx-1
  1346. const StringUtils::StringUTF8::CharUTF8Store& str = text.getString();
  1347. auto it = std::find_if(str.rbegin() + (str.size() - idx + 1), str.rend(), isUTF8CharWrappable);
  1348. if (it == str.rend())
  1349. return -1;
  1350. return static_cast<int>(it.base() - str.begin());
  1351. }
  1352. int getNextWordPos(const StringUtils::StringUTF8& text, int idx)
  1353. {
  1354. const StringUtils::StringUTF8::CharUTF8Store& str = text.getString();
  1355. if (idx + 1 >= static_cast<int>(str.size()))
  1356. return static_cast<int>(str.size());
  1357. auto it = std::find_if(str.begin() + idx + 1, str.end(), isUTF8CharWrappable);
  1358. return static_cast<int>(it - str.begin());
  1359. }
  1360. bool isWrappable(const StringUtils::StringUTF8& text)
  1361. {
  1362. const StringUtils::StringUTF8::CharUTF8Store& str = text.getString();
  1363. return std::any_of(str.begin(), str.end(), isUTF8CharWrappable);
  1364. }
  1365. int findSplitPositionForWord(Label* label, const StringUtils::StringUTF8& text, int estimatedIdx, float originalLeftSpaceWidth, float newLineWidth)
  1366. {
  1367. bool startingNewLine = (newLineWidth == originalLeftSpaceWidth);
  1368. if (!isWrappable(text))
  1369. return (startingNewLine ? static_cast<int>(text.length()) : 0);
  1370. // The adjustment of the new line position
  1371. int idx = getNextWordPos(text, estimatedIdx);
  1372. std::string leftStr = text.getAsCharSequence(0, idx);
  1373. label->setString(leftStr);
  1374. float textRendererWidth = label->getContentSize().width;
  1375. if (originalLeftSpaceWidth < textRendererWidth) // Have protruding
  1376. {
  1377. while (1)
  1378. {
  1379. // try to erase a word
  1380. int newidx = getPrevWordPos(text, idx);
  1381. if (newidx >= 0)
  1382. {
  1383. leftStr = text.getAsCharSequence(0, newidx);
  1384. label->setString(leftStr);
  1385. textRendererWidth = label->getContentSize().width;
  1386. if (textRendererWidth <= originalLeftSpaceWidth) // is fitted
  1387. return newidx;
  1388. idx = newidx;
  1389. continue;
  1390. }
  1391. // newidx < 0 means no prev word
  1392. return (startingNewLine ? idx : 0);
  1393. }
  1394. }
  1395. else if (textRendererWidth < originalLeftSpaceWidth) // A wide margin
  1396. {
  1397. while (1)
  1398. {
  1399. // try to append a word
  1400. int newidx = getNextWordPos(text, idx);
  1401. leftStr = text.getAsCharSequence(0, newidx);
  1402. label->setString(leftStr);
  1403. textRendererWidth = label->getContentSize().width;
  1404. if (textRendererWidth < originalLeftSpaceWidth)
  1405. {
  1406. // the whole string is tested
  1407. if (newidx == static_cast<int>(text.length()))
  1408. return newidx;
  1409. idx = newidx;
  1410. continue;
  1411. }
  1412. // protruded ? undo add, or quite fit
  1413. return (textRendererWidth > originalLeftSpaceWidth ? idx : newidx);
  1414. }
  1415. }
  1416. return idx;
  1417. }
  1418. int findSplitPositionForChar(Label* label, const StringUtils::StringUTF8& text, int estimatedIdx, float originalLeftSpaceWidth, float newLineWidth)
  1419. {
  1420. bool startingNewLine = (newLineWidth == originalLeftSpaceWidth);
  1421. int stringLength = static_cast<int>(text.length());
  1422. int leftLength = estimatedIdx;
  1423. // The adjustment of the new line position
  1424. std::string leftStr = text.getAsCharSequence(0, leftLength);
  1425. label->setString(leftStr);
  1426. float textRendererWidth = label->getContentSize().width;
  1427. if (originalLeftSpaceWidth < textRendererWidth) // Have protruding
  1428. {
  1429. while (leftLength-- > 0)
  1430. {
  1431. // try to erase a char
  1432. auto& ch = text.getString().at(leftLength);
  1433. leftStr.erase(leftStr.end() - ch._char.length(), leftStr.end());
  1434. label->setString(leftStr);
  1435. textRendererWidth = label->getContentSize().width;
  1436. if (textRendererWidth <= originalLeftSpaceWidth) // is fitted
  1437. break;
  1438. }
  1439. }
  1440. else if (textRendererWidth < originalLeftSpaceWidth) // A wide margin
  1441. {
  1442. while (leftLength < stringLength)
  1443. {
  1444. // try to append a char
  1445. auto& ch = text.getString().at(leftLength);
  1446. ++leftLength;
  1447. leftStr.append(ch._char);
  1448. label->setString(leftStr);
  1449. textRendererWidth = label->getContentSize().width;
  1450. if (originalLeftSpaceWidth < textRendererWidth) // protruded, undo add
  1451. {
  1452. --leftLength;
  1453. break;
  1454. }
  1455. else if (originalLeftSpaceWidth == textRendererWidth) // quite fit
  1456. {
  1457. break;
  1458. }
  1459. }
  1460. }
  1461. if (leftLength <= 0)
  1462. return (startingNewLine) ? 1 : 0;
  1463. return leftLength;
  1464. }
  1465. }
  1466. void RichText::handleTextRenderer(const std::string& text, const std::string& fontName, float fontSize, const Color3B &color,
  1467. GLubyte opacity, uint32_t flags, const std::string& url,
  1468. const Color3B& outlineColor, int outlineSize ,
  1469. const Color3B& shadowColor, const Size& shadowOffset, int shadowBlurRadius,
  1470. const Color3B& glowColor)
  1471. {
  1472. bool fileExist = FileUtils::getInstance()->isFileExist(fontName);
  1473. RichText::WrapMode wrapMode = static_cast<RichText::WrapMode>(_defaults.at(KEY_WRAP_MODE).asInt());
  1474. // split text by \n
  1475. std::stringstream ss(text);
  1476. std::string currentText;
  1477. size_t realLines = 0;
  1478. while (std::getline(ss, currentText, '\n'))
  1479. {
  1480. if (realLines > 0)
  1481. {
  1482. addNewLine();
  1483. _lineHeights.back() = fontSize;
  1484. }
  1485. ++realLines;
  1486. size_t splitParts = 0;
  1487. StringUtils::StringUTF8 utf8Text(currentText);
  1488. while (!currentText.empty())
  1489. {
  1490. if (splitParts > 0)
  1491. {
  1492. addNewLine();
  1493. _lineHeights.back() = fontSize;
  1494. }
  1495. ++splitParts;
  1496. Label* textRenderer = fileExist ? Label::createWithTTF(currentText, fontName, fontSize)
  1497. : Label::createWithSystemFont(currentText, fontName, fontSize);
  1498. if (flags & RichElementText::ITALICS_FLAG)
  1499. textRenderer->enableItalics();
  1500. if (flags & RichElementText::BOLD_FLAG)
  1501. textRenderer->enableBold();
  1502. if (flags & RichElementText::UNDERLINE_FLAG)
  1503. textRenderer->enableUnderline();
  1504. if (flags & RichElementText::STRIKETHROUGH_FLAG)
  1505. textRenderer->enableStrikethrough();
  1506. if (flags & RichElementText::URL_FLAG)
  1507. textRenderer->addComponent(ListenerComponent::create(textRenderer,
  1508. url,
  1509. std::bind(&RichText::openUrl, this, std::placeholders::_1)));
  1510. if (flags & RichElementText::OUTLINE_FLAG)
  1511. textRenderer->enableOutline(Color4B(outlineColor), outlineSize);
  1512. if (flags & RichElementText::SHADOW_FLAG)
  1513. textRenderer->enableShadow(Color4B(shadowColor), shadowOffset, shadowBlurRadius);
  1514. if (flags & RichElementText::GLOW_FLAG)
  1515. textRenderer->enableGlow(Color4B(glowColor));
  1516. textRenderer->setColor(color);
  1517. textRenderer->setOpacity(opacity);
  1518. // textRendererWidth will get 0.0f, when we've got glError: 0x0501 in Label::getContentSize
  1519. // It happens when currentText is very very long so that can't generate a texture
  1520. float textRendererWidth = textRenderer->getContentSize().width;
  1521. // no splitting
  1522. if (textRendererWidth > 0.0f && _leftSpaceWidth >= textRendererWidth)
  1523. {
  1524. _leftSpaceWidth -= textRendererWidth;
  1525. pushToContainer(textRenderer);
  1526. break;
  1527. }
  1528. // rough estimate
  1529. // when textRendererWidth == 0.0f, use fontSize as the rough estimate of width for each char,
  1530. // (_leftSpaceWidth / fontSize) means how many chars can be aligned in leftSpaceWidth.
  1531. int estimatedIdx = 0;
  1532. if (textRendererWidth > 0.0f)
  1533. estimatedIdx = static_cast<int>(_leftSpaceWidth / textRendererWidth * utf8Text.length());
  1534. else
  1535. estimatedIdx = static_cast<int>(_leftSpaceWidth / fontSize);
  1536. int leftLength = 0;
  1537. if (wrapMode == WRAP_PER_WORD)
  1538. leftLength = findSplitPositionForWord(textRenderer, utf8Text, estimatedIdx, _leftSpaceWidth, _customSize.width);
  1539. else
  1540. leftLength = findSplitPositionForChar(textRenderer, utf8Text, estimatedIdx, _leftSpaceWidth, _customSize.width);
  1541. // split string
  1542. if (leftLength > 0)
  1543. {
  1544. textRenderer->setString(utf8Text.getAsCharSequence(0, leftLength));
  1545. pushToContainer(textRenderer);
  1546. }
  1547. // skip spaces
  1548. StringUtils::StringUTF8::CharUTF8Store& str = utf8Text.getString();
  1549. int rightStart = leftLength;
  1550. while (rightStart < (int)str.size() && str[rightStart].isASCII() && std::isspace(str[rightStart]._char[0], std::locale()))
  1551. ++rightStart;
  1552. // erase the chars which are processed
  1553. str.erase(str.begin(), str.begin() + rightStart);
  1554. currentText = utf8Text.getAsCharSequence();
  1555. }
  1556. }
  1557. }
  1558. void RichText::handleImageRenderer(const std::string& filePath, const Color3B &/*color*/, GLubyte /*opacity*/, int width, int height, const std::string& url)
  1559. {
  1560. Sprite* imageRenderer = Sprite::create(filePath);
  1561. if (imageRenderer)
  1562. {
  1563. auto currentSize = imageRenderer->getContentSize();
  1564. if (width != -1)
  1565. imageRenderer->setScaleX(width / currentSize.width);
  1566. if (height != -1)
  1567. imageRenderer->setScaleY(height / currentSize.height);
  1568. imageRenderer->setContentSize(Size(currentSize.width * imageRenderer->getScaleX(),
  1569. currentSize.height * imageRenderer->getScaleY()));
  1570. imageRenderer->setScale(1.f, 1.f);
  1571. handleCustomRenderer(imageRenderer);
  1572. imageRenderer->addComponent(ListenerComponent::create(imageRenderer,
  1573. url,
  1574. std::bind(&RichText::openUrl, this, std::placeholders::_1)));
  1575. }
  1576. }
  1577. void RichText::handleCustomRenderer(cocos2d::Node *renderer)
  1578. {
  1579. Size imgSize = renderer->getContentSize();
  1580. _leftSpaceWidth -= imgSize.width;
  1581. if (_leftSpaceWidth < 0.0f)
  1582. {
  1583. addNewLine();
  1584. pushToContainer(renderer);
  1585. _leftSpaceWidth -= imgSize.width;
  1586. }
  1587. else
  1588. {
  1589. pushToContainer(renderer);
  1590. }
  1591. }
  1592. void RichText::addNewLine()
  1593. {
  1594. _leftSpaceWidth = _customSize.width;
  1595. _elementRenders.emplace_back();
  1596. _lineHeights.emplace_back();
  1597. }
  1598. void RichText::formatRenderers()
  1599. {
  1600. float verticalSpace = _defaults[KEY_VERTICAL_SPACE].asFloat();
  1601. float fontSize = _defaults[KEY_FONT_SIZE].asFloat();
  1602. if (_ignoreSize)
  1603. {
  1604. float newContentSizeWidth = 0.0f;
  1605. float nextPosY = 0.0f;
  1606. std::vector<std::pair<Vector<Node*>*, float> > rowWidthPairs;
  1607. rowWidthPairs.reserve(_elementRenders.size());
  1608. for (auto& element: _elementRenders)
  1609. {
  1610. float nextPosX = 0.0f;
  1611. float maxY = 0.0f;
  1612. for (auto& iter : element)
  1613. {
  1614. iter->setAnchorPoint(Vec2::ZERO);
  1615. iter->setPosition(nextPosX, nextPosY);
  1616. this->addProtectedChild(iter, 1);
  1617. Size iSize = iter->getContentSize();
  1618. newContentSizeWidth += iSize.width;
  1619. nextPosX += iSize.width;
  1620. maxY = std::max(maxY, iSize.height);
  1621. }
  1622. nextPosY -= maxY;
  1623. rowWidthPairs.emplace_back(&element, nextPosX);
  1624. }
  1625. this->setContentSize(Size(newContentSizeWidth, -nextPosY));
  1626. for ( auto& row : rowWidthPairs )
  1627. doHorizontalAlignment(*row.first, row.second);
  1628. }
  1629. else
  1630. {
  1631. // calculate real height
  1632. float newContentSizeHeight = 0.0f;
  1633. std::vector<float> maxHeights(_elementRenders.size());
  1634. for (size_t i=0, size = _elementRenders.size(); i<size; i++)
  1635. {
  1636. Vector<Node*>& row = _elementRenders[i];
  1637. float maxHeight = 0.0f;
  1638. for (auto& iter : row)
  1639. {
  1640. maxHeight = std::max(iter->getContentSize().height, maxHeight);
  1641. }
  1642. // gap for empty line, if _lineHeights[i] == 0, use current RichText's fontSize
  1643. if (row.empty())
  1644. {
  1645. maxHeight = (_lineHeights[i] != 0.0f ? _lineHeights[i] : fontSize);
  1646. }
  1647. maxHeights[i] = maxHeight;
  1648. // vertical space except for first line
  1649. newContentSizeHeight += (i != 0 ? maxHeight + verticalSpace : maxHeight);
  1650. }
  1651. _customSize.height = newContentSizeHeight;
  1652. // align renders
  1653. float nextPosY = _customSize.height;
  1654. for (size_t i=0, size = _elementRenders.size(); i<size; i++)
  1655. {
  1656. Vector<Node*>& row = _elementRenders[i];
  1657. float nextPosX = 0.0f;
  1658. nextPosY -= (i != 0 ? maxHeights[i] + verticalSpace : maxHeights[i]);
  1659. for (auto& iter : row)
  1660. {
  1661. iter->setAnchorPoint(Vec2::ZERO);
  1662. iter->setPosition(nextPosX, nextPosY);
  1663. this->addProtectedChild(iter, 1);
  1664. nextPosX += iter->getContentSize().width;
  1665. }
  1666. doHorizontalAlignment(row, nextPosX);
  1667. }
  1668. }
  1669. _elementRenders.clear();
  1670. _lineHeights.clear();
  1671. if (_ignoreSize)
  1672. {
  1673. Size s = getVirtualRendererSize();
  1674. this->setContentSize(s);
  1675. }
  1676. else
  1677. {
  1678. this->setContentSize(_customSize);
  1679. }
  1680. updateContentSizeWithTextureSize(_contentSize);
  1681. }
  1682. namespace {
  1683. float getPaddingAmount(const RichText::HorizontalAlignment alignment, const float leftOver) {
  1684. switch ( alignment ) {
  1685. case RichText::HorizontalAlignment::CENTER:
  1686. return leftOver / 2.f;
  1687. case RichText::HorizontalAlignment::RIGHT:
  1688. return leftOver;
  1689. default:
  1690. CCASSERT(false, "invalid horizontal alignment!");
  1691. return 0.f;
  1692. }
  1693. }
  1694. }
  1695. void RichText::doHorizontalAlignment(const Vector<cocos2d::Node*> &row, float rowWidth) {
  1696. const auto alignment = static_cast<HorizontalAlignment>(_defaults.at(KEY_HORIZONTAL_ALIGNMENT).asInt());
  1697. if ( alignment != HorizontalAlignment::LEFT ) {
  1698. const auto diff = stripTrailingWhitespace(row);
  1699. const auto leftOver = getContentSize().width - (rowWidth + diff);
  1700. const float leftPadding = getPaddingAmount(alignment, leftOver);
  1701. const Vec2 offset(leftPadding, 0.f);
  1702. for ( auto& node : row ) {
  1703. node->setPosition(node->getPosition() + offset);
  1704. }
  1705. }
  1706. }
  1707. namespace {
  1708. bool isWhitespace(char c) {
  1709. return std::isspace(c, std::locale());
  1710. }
  1711. std::string rtrim(std::string s) {
  1712. s.erase(std::find_if_not(s.rbegin(),
  1713. s.rend(),
  1714. isWhitespace).base(),
  1715. s.end());
  1716. return s;
  1717. }
  1718. }
  1719. float RichText::stripTrailingWhitespace(const Vector<cocos2d::Node*>& row) {
  1720. if ( !row.empty() ) {
  1721. if ( auto label = dynamic_cast<Label*>(row.back()) ) {
  1722. const auto width = label->getContentSize().width;
  1723. const auto trimmedString = rtrim(label->getString());
  1724. if ( label->getString() != trimmedString ) {
  1725. label->setString(trimmedString);
  1726. return label->getContentSize().width - width;
  1727. }
  1728. }
  1729. }
  1730. return 0.0f;
  1731. }
  1732. void RichText::adaptRenderers()
  1733. {
  1734. this->formatText();
  1735. }
  1736. void RichText::pushToContainer(cocos2d::Node *renderer)
  1737. {
  1738. if (_elementRenders.size() <= 0)
  1739. {
  1740. return;
  1741. }
  1742. _elementRenders[_elementRenders.size()-1].pushBack(renderer);
  1743. }
  1744. void RichText::setVerticalSpace(float space)
  1745. {
  1746. _defaults[KEY_VERTICAL_SPACE] = space;
  1747. }
  1748. void RichText::ignoreContentAdaptWithSize(bool ignore)
  1749. {
  1750. if (_ignoreSize != ignore)
  1751. {
  1752. _formatTextDirty = true;
  1753. Widget::ignoreContentAdaptWithSize(ignore);
  1754. }
  1755. }
  1756. std::string RichText::getDescription() const
  1757. {
  1758. return "RichText";
  1759. }