diff --git a/AUTHORS b/AUTHORS index fe221961d8..9ca22a8986 100644 --- a/AUTHORS +++ b/AUTHORS @@ -722,6 +722,7 @@ Developers: Pisces000221 Corrected a few mistakes in the README file of project-creator. Corrected a mistake in README. + Fixed a bug that a string which only contains CJK characters can't make a line-break when it's needed. hbbalfred Fixed a bug that crash if file doesn't exist when using FileUtils::getStringFromFile. diff --git a/CHANGELOG b/CHANGELOG index 3a5ef7c1fb..3ea1b6a59c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ cocos2d-x-3.0rc0 Feb.?? 2014 [NEW] Console: Added 'resolution', 'projection' commands. Improved API [NEW] Director: Displays 'Vertices drawn' in the stats. Useful to measure performance. + [FIX] A string which only contains CJK characters can't make a line-break when it's needed. [FIX] EGLView improvements: renamed to GLView, no longer a singleton, easier to customize [FIX] Removes samples except testcpp|testjavascript|testlua. Moves sample games to `cocos2d/samples` repo. [FIX] cc.BuilderReader.load( path, null, parentSize ); was not allowed. diff --git a/cocos/2d/CCEventDispatcher.cpp b/cocos/2d/CCEventDispatcher.cpp index 9c2603b57c..2d9d18d169 100644 --- a/cocos/2d/CCEventDispatcher.cpp +++ b/cocos/2d/CCEventDispatcher.cpp @@ -583,7 +583,7 @@ void EventDispatcher::dispatchEventToListeners(EventListenerVector* listeners, s { auto l = fixedPriorityListeners->at(i); - if (!l->isPaused() && l->isRegistered() && onEvent(fixedPriorityListeners->at(i))) + if (!l->isPaused() && l->isRegistered() && onEvent(l)) { shouldStopPropagation = true; break; @@ -643,28 +643,28 @@ void EventDispatcher::dispatchTouchEvent(EventTouch* event) sortEventListeners(EventListenerTouchOneByOne::LISTENER_ID); sortEventListeners(EventListenerTouchAllAtOnce::LISTENER_ID); - auto oneByOnelisteners = getListeners(EventListenerTouchOneByOne::LISTENER_ID); - auto allAtOncelisteners = getListeners(EventListenerTouchAllAtOnce::LISTENER_ID); + auto oneByOneListeners = getListeners(EventListenerTouchOneByOne::LISTENER_ID); + auto allAtOnceListeners = getListeners(EventListenerTouchAllAtOnce::LISTENER_ID); // If there aren't any touch listeners, return directly. - if (nullptr == oneByOnelisteners && nullptr == allAtOncelisteners) + if (nullptr == oneByOneListeners && nullptr == allAtOnceListeners) return; - bool isNeedsMutableSet = (oneByOnelisteners && allAtOncelisteners); + bool isNeedsMutableSet = (oneByOneListeners && allAtOnceListeners); - std::vector orignalTouches = event->getTouches(); - std::vector mutableTouches(orignalTouches.size()); - std::copy(orignalTouches.begin(), orignalTouches.end(), mutableTouches.begin()); + std::vector originalTouches = event->getTouches(); + std::vector mutableTouches(originalTouches.size()); + std::copy(originalTouches.begin(), originalTouches.end(), mutableTouches.begin()); // // process the target handlers 1st // - if (oneByOnelisteners) + if (oneByOneListeners) { auto mutableTouchesIter = mutableTouches.begin(); - auto touchesIter = orignalTouches.begin(); + auto touchesIter = originalTouches.begin(); - for (; touchesIter != orignalTouches.end(); ++touchesIter) + for (; touchesIter != originalTouches.end(); ++touchesIter) { bool isSwallowed = false; @@ -755,7 +755,7 @@ void EventDispatcher::dispatchTouchEvent(EventTouch* event) }; // - dispatchEventToListeners(oneByOnelisteners, onTouchEvent); + dispatchEventToListeners(oneByOneListeners, onTouchEvent); if (event->isStopped()) { return; @@ -769,7 +769,7 @@ void EventDispatcher::dispatchTouchEvent(EventTouch* event) // // process standard handlers 2nd // - if (allAtOncelisteners && mutableTouches.size() > 0) + if (allAtOnceListeners && mutableTouches.size() > 0) { auto onTouchesEvent = [&](EventListener* l) -> bool{ @@ -821,7 +821,7 @@ void EventDispatcher::dispatchTouchEvent(EventTouch* event) return false; }; - dispatchEventToListeners(allAtOncelisteners, onTouchesEvent); + dispatchEventToListeners(allAtOnceListeners, onTouchesEvent); if (event->isStopped()) { return; @@ -891,11 +891,7 @@ void EventDispatcher::updateListeners(Event* event) { _priorityDirtyFlagMap.erase(listenersIter->first); delete listenersIter->second; - listenersIter = _listeners.erase(listenersIter); - } - else - { - ++listenersIter; + _listeners.erase(listenersIter); } }; @@ -981,14 +977,14 @@ void EventDispatcher::sortEventListenersOfSceneGraphPriority(const EventListener visitTarget(rootNode, true); // After sort: priority < 0, > 0 - auto sceneGraphlisteners = listeners->getSceneGraphPriorityListeners(); - std::sort(sceneGraphlisteners->begin(), sceneGraphlisteners->end(), [this](const EventListener* l1, const EventListener* l2) { + auto sceneGraphListeners = listeners->getSceneGraphPriorityListeners(); + std::sort(sceneGraphListeners->begin(), sceneGraphListeners->end(), [this](const EventListener* l1, const EventListener* l2) { return _nodePriorityMap[l1->getSceneGraphPriority()] > _nodePriorityMap[l2->getSceneGraphPriority()]; }); #if DUMP_LISTENER_ITEM_PRIORITY_INFO log("-----------------------------------"); - for (auto& l : *sceneGraphlisteners) + for (auto& l : *sceneGraphListeners) { log("listener priority: node ([%s]%p), priority (%d)", typeid(*l->_node).name(), l->_node, _nodePriorityMap[l->_node]); } @@ -1003,14 +999,14 @@ void EventDispatcher::sortEventListenersOfFixedPriority(const EventListener::Lis return; // After sort: priority < 0, > 0 - auto fixedlisteners = listeners->getFixedPriorityListeners(); - std::sort(fixedlisteners->begin(), fixedlisteners->end(), [](const EventListener* l1, const EventListener* l2) { + auto fixedListeners = listeners->getFixedPriorityListeners(); + std::sort(fixedListeners->begin(), fixedListeners->end(), [](const EventListener* l1, const EventListener* l2) { return l1->getFixedPriority() < l2->getFixedPriority(); }); // FIXME: Should use binary search int index = 0; - for (auto& listener : *fixedlisteners) + for (auto& listener : *fixedListeners) { if (listener->getFixedPriority() >= 0) break; @@ -1021,7 +1017,7 @@ void EventDispatcher::sortEventListenersOfFixedPriority(const EventListener::Lis #if DUMP_LISTENER_ITEM_PRIORITY_INFO log("-----------------------------------"); - for (auto& l : *fixedlisteners) + for (auto& l : *fixedListeners) { log("listener priority: node (%p), fixed (%d)", l->_node, l->_fixedPriority); } diff --git a/cocos/2d/CCLabelTextFormatter.cpp b/cocos/2d/CCLabelTextFormatter.cpp index 0ed58fbce2..308e0957ec 100644 --- a/cocos/2d/CCLabelTextFormatter.cpp +++ b/cocos/2d/CCLabelTextFormatter.cpp @@ -107,26 +107,41 @@ bool LabelTextFormatter::multilineText(Label *theLabel) startOfLine = startOfWord; isStartOfLine = true; } - - // Whitespace. - if (isspace_unicode(character)) + + // 1) Whitespace. + // 2) This character is non-CJK, but the last character is CJK + if (isspace_unicode(character) || + !last_word.empty() && iscjk_unicode(last_word.back()) && !iscjk_unicode(character)) { - last_word.push_back(character); + // if current character is white space, put it into the current word + if (isspace_unicode(character)) last_word.push_back(character); multiline_string.insert(multiline_string.end(), last_word.begin(), last_word.end()); last_word.clear(); isStartOfWord = false; startOfWord = -1; + // put the CJK character in the last word + // and put the non-CJK(ASCII) character in the current word + if (!isspace_unicode(character)) last_word.push_back(character); continue; } + + // CJK characters. + if (iscjk_unicode(character)) + { + multiline_string.insert(multiline_string.end(), last_word.begin(), last_word.end()); + last_word.clear(); + isStartOfWord = false; + startOfWord = -1; + } float posRight = (info->position.x + info->contentSize.width) * scalsX; // Out of bounds. if (posRight - startOfLine > lineWidth) { - if (!breakLineWithoutSpace) + if (!breakLineWithoutSpace && !iscjk_unicode(character)) { last_word.push_back(character); - + int found = cc_utf8_find_last_not_char(multiline_string, ' '); if (found != -1) cc_utf8_trim_ws(&multiline_string); @@ -145,6 +160,7 @@ bool LabelTextFormatter::multilineText(Label *theLabel) cc_utf8_trim_ws(&last_word); last_word.push_back('\n'); + multiline_string.insert(multiline_string.end(), last_word.begin(), last_word.end()); last_word.clear(); isStartOfWord = false; @@ -349,4 +365,4 @@ bool LabelTextFormatter::createStringSprites(Label *theLabel) return true; } -NS_CC_END \ No newline at end of file +NS_CC_END diff --git a/cocos/2d/ccUTF8.cpp b/cocos/2d/ccUTF8.cpp index 7ebf8a0e31..33543ba8b1 100644 --- a/cocos/2d/ccUTF8.cpp +++ b/cocos/2d/ccUTF8.cpp @@ -171,6 +171,18 @@ bool isspace_unicode(unsigned short ch) || ch == 0x205F || ch == 0x3000; } +bool iscjk_unicode(unsigned short ch) +{ + return (ch >= 0x4E00 && ch <= 0x9FBF) // CJK Unified Ideographs + || (ch >= 0x2E80 && ch <= 0x2FDF) // CJK Radicals Supplement & Kangxi Radicals + || (ch >= 0x2FF0 && ch <= 0x30FF) // Ideographic Description Characters, CJK Symbols and Punctuation & Japanese + || (ch >= 0x3100 && ch <= 0x31BF) // Korean + || (ch >= 0xAC00 && ch <= 0xD7AF) // Hangul Syllables + || (ch >= 0xF900 && ch <= 0xFAFF) // CJK Compatibility Ideographs + || (ch >= 0xFE30 && ch <= 0xFE4F) // CJK Compatibility Forms + || (ch >= 0x31C0 && ch <= 0x4DFF); // Other exiensions +} + void cc_utf8_trim_ws(std::vector* str) { int len = static_cast(str->size()); diff --git a/cocos/2d/ccUTF8.h b/cocos/2d/ccUTF8.h index 352a4db41a..e3e16da3c7 100644 --- a/cocos/2d/ccUTF8.h +++ b/cocos/2d/ccUTF8.h @@ -41,6 +41,17 @@ CC_DLL void cc_utf8_trim_ws(std::vector* str); * */ CC_DLL bool isspace_unicode(unsigned short ch); +/** + * Whether the character is a Chinese/Japanese/Korean character. + * + * @param ch the unicode character + * @returns whether the character is a Chinese character. + * + * @see http://www.searchtb.com/2012/04/chinese_encode.html + * @see http://tieba.baidu.com/p/748765987 + * */ +CC_DLL bool iscjk_unicode(unsigned short ch); + /** * Returns the length of the string in characters. * diff --git a/tests/test-cpp/Classes/LabelTest/LabelTestNew.cpp b/tests/test-cpp/Classes/LabelTest/LabelTestNew.cpp index e0ee9e3a5a..544b76d46a 100644 --- a/tests/test-cpp/Classes/LabelTest/LabelTestNew.cpp +++ b/tests/test-cpp/Classes/LabelTest/LabelTestNew.cpp @@ -63,6 +63,7 @@ static std::function createFunctions[] = CL(LabelTTFColor), CL(LabelTTFFontsTestNew), CL(LabelTTFDynamicAlignment), + CL(LabelTTFCJKWrappingTest), CL(LabelTTFUnicodeNew), CL(LabelBMFontTestNew), CL(LabelTTFDistanceField), @@ -1071,6 +1072,56 @@ std::string LabelTTFDynamicAlignment::subtitle() const return "Uses the new Label with TTF. Testing alignment"; } +// +// NewLabelTTF Chinese/Japanese/Korean wrapping test +// +LabelTTFCJKWrappingTest::LabelTTFCJKWrappingTest() +{ + auto size = Director::getInstance()->getWinSize(); + + auto drawNode = DrawNode::create(); + drawNode->setAnchorPoint(Point(0, 0)); + this->addChild(drawNode); + drawNode->drawSegment( + Point(size.width * 0.1, size.height * 0.8), + Point(size.width * 0.1, 0), 1, Color4F(1, 0, 0, 1)); + drawNode->drawSegment( + Point(size.width * 0.85, size.height * 0.8), + Point(size.width * 0.85, 0), 1, Color4F(1, 0, 0, 1)); + + TTFConfig ttfConfig("fonts/wt021.ttf", 50, GlyphCollection::DYNAMIC); + auto label1 = Label::createWithTTF(ttfConfig, + "你好,Cocos2d-x v3的New Label。", TextHAlignment::LEFT, size.width * 0.75); + label1->setColor(Color3B(128, 255, 255)); + label1->setPosition(Point(size.width * 0.1, size.height * 0.6)); + label1->setAnchorPoint(Point(0, 0.5)); + this->addChild(label1); + + auto label2 = Label::createWithTTF(ttfConfig, + "早上好,Cocos2d-x v3的New Label。", TextHAlignment::LEFT, size.width * 0.75); + label2->setColor(Color3B(255, 128, 255)); + label2->setPosition(Point(size.width * 0.1, size.height * 0.4)); + label2->setAnchorPoint(Point(0, 0.5)); + this->addChild(label2); + auto label3 = Label::createWithTTF(ttfConfig, + "美好的一天啊美好的一天啊美好的一天啊", TextHAlignment::LEFT, size.width * 0.75); + label3->setColor(Color3B(255, 255, 128)); + label3->setPosition(Point(size.width * 0.1, size.height * 0.2)); + label3->setAnchorPoint(Point(0, 0.5)); + this->addChild(label3); +} + +std::string LabelTTFCJKWrappingTest::title() const +{ + return "New Label + .TTF"; +} + +std::string LabelTTFCJKWrappingTest::subtitle() const +{ + return "New Label with CJK + ASCII characters\n" + "Characters should stay in the correct position"; +} + // // NewLabelTTF unicode test // diff --git a/tests/test-cpp/Classes/LabelTest/LabelTestNew.h b/tests/test-cpp/Classes/LabelTest/LabelTestNew.h index 3d5e99f368..226de1773c 100644 --- a/tests/test-cpp/Classes/LabelTest/LabelTestNew.h +++ b/tests/test-cpp/Classes/LabelTest/LabelTestNew.h @@ -287,6 +287,19 @@ private: }; +class LabelTTFCJKWrappingTest : public AtlasDemoNew +{ +public: + CREATE_FUNC(LabelTTFCJKWrappingTest); + + LabelTTFCJKWrappingTest(); + virtual std::string title() const override; + virtual std::string subtitle() const override; + +private: +}; + + class LabelTTFFontsTestNew : public AtlasDemoNew { public: diff --git a/tests/test-lua/Resources/luaScript/LabelTestNew/LabelTestNew.lua b/tests/test-lua/Resources/luaScript/LabelTestNew/LabelTestNew.lua index 0bcdadd979..72011b9237 100644 --- a/tests/test-lua/Resources/luaScript/LabelTestNew/LabelTestNew.lua +++ b/tests/test-lua/Resources/luaScript/LabelTestNew/LabelTestNew.lua @@ -1008,6 +1008,64 @@ function LabelTTFDynamicAlignment.setAlignmentRight(pSender) end +-------------------------------------------------------- +----- LabelTTFCJKWrappingTest +-------------------------------------------------------- +local LabelTTFCJKWrappingTest = {} +function LabelTTFCJKWrappingTest.create() + local layer = cc.Layer:create() + Helper.initWithLayer(layer) + Helper.titleLabel:setString("New Label + .TTF") + Helper.subtitleLabel:setString( + "New Label with CJK + ASCII characters\n" + .. "Characters should stay in the correct position") + + local size = cc.Director:getInstance():getVisibleSize() + local ttfConfig = {} + ttfConfig.fontFilePath = "fonts/wt021.ttf" + ttfConfig.fontSize = 50 + ttfConfig.glyphs = cc.GLYPHCOLLECTION_DYNAMIC + ttfConfig.customGlyphs = nil + ttfConfig.distanceFieldEnabled = true + + local drawNode = cc.DrawNode:create() + drawNode:setAnchorPoint(cc.p(0, 0)) + layer:addChild(drawNode) + drawNode:drawSegment( + cc.p(size.width * 0.1, size.height * 0.8), + cc.p(size.width * 0.1, 0), 1, cc.c4f(1, 0, 0, 1)) + drawNode:drawSegment( + cc.p(size.width * 0.85, size.height * 0.8), + cc.p(size.width * 0.85, 0), 1, cc.c4f(1, 0, 0, 1)) + + local label1 = cc.Label:createWithTTF( + ttfConfig, "你好,Cocos2d-x v3的New Label。", + cc.TEXT_ALIGNMENT_LEFT, size.width * 0.75) + label1:setColor(cc.c3b(128, 255, 255)) + label1:setPosition(cc.p(size.width * 0.1, size.height * 0.6)) + label1:setAnchorPoint(cc.p(0, 0.5)) + layer:addChild(label1) + + local label2 = cc.Label:createWithTTF( + ttfConfig, "早上好,Cocos2d-x v3的New Label。", + cc.TEXT_ALIGNMENT_LEFT, size.width * 0.75) + label2:setColor(cc.c3b(255, 128, 255)) + label2:setPosition(cc.p(size.width * 0.1, size.height * 0.4)) + label2:setAnchorPoint(cc.p(0, 0.5)) + layer:addChild(label2) + + local label3 = cc.Label:createWithTTF( + ttfConfig, "美好的一天啊美好的一天啊美好的一天啊", + cc.TEXT_ALIGNMENT_LEFT, size.width * 0.75) + label3:setColor(cc.c3b(255, 255, 128)) + label3:setPosition(cc.p(size.width * 0.1, size.height * 0.2)) + label3:setAnchorPoint(cc.p(0, 0.5)) + layer:addChild(label3) + + return layer +end + + -------------------------------------------------------- ----- LabelTTFFontsTestNew -------------------------------------------------------- @@ -1262,6 +1320,7 @@ function LabelTestNew() LabelFNTBounds.create, LabelTTFLongLineWrapping.create, LabelTTFDynamicAlignment.create, + LabelTTFCJKWrappingTest.create, LabelTTFFontsTestNew.create, LabelBMFontTestNew.create, LabelTTFDistanceField.create,