mirror of https://github.com/axmolengine/axmol.git
781 lines
21 KiB
C++
781 lines
21 KiB
C++
|
#include "FUIRichText.h"
|
||
|
|
||
|
#include <sstream>
|
||
|
#include <vector>
|
||
|
#include <locale>
|
||
|
#include <algorithm>
|
||
|
|
||
|
#include "utils/ToolSet.h"
|
||
|
#include "UIPackage.h"
|
||
|
|
||
|
NS_FGUI_BEGIN
|
||
|
USING_NS_CC;
|
||
|
|
||
|
using namespace std;
|
||
|
|
||
|
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT)
|
||
|
#define strcasecmp _stricmp
|
||
|
#endif
|
||
|
|
||
|
static const int GUTTER_X = 2;
|
||
|
static const int GUTTER_Y = 2;
|
||
|
|
||
|
static int getPrevWord(const std::string& text, int idx)
|
||
|
{
|
||
|
// start from idx-1
|
||
|
for (int i = idx - 1; i >= 0; --i)
|
||
|
{
|
||
|
if (!std::isalnum(text[i], std::locale()))
|
||
|
return i;
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
static bool isWrappable(const std::string& text)
|
||
|
{
|
||
|
for (size_t i = 0, size = text.length(); i < size; ++i)
|
||
|
{
|
||
|
if (!std::isalnum(text[i], std::locale()))
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static float getPaddingAmount(TextHAlignment alignment, const float leftOver) {
|
||
|
switch (alignment) {
|
||
|
case TextHAlignment::CENTER:
|
||
|
return leftOver / 2.f;
|
||
|
case TextHAlignment::RIGHT:
|
||
|
return leftOver;
|
||
|
default:
|
||
|
CCASSERT(false, "invalid horizontal alignment!");
|
||
|
return 0.f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static bool isWhitespace(char c) {
|
||
|
return std::isspace(c, std::locale());
|
||
|
}
|
||
|
|
||
|
static void ltrim(std::string& s) {
|
||
|
s.erase(s.begin(), std::find_if_not(s.begin(),
|
||
|
s.end(),
|
||
|
isWhitespace));
|
||
|
}
|
||
|
|
||
|
static void rtrim(std::string& s) {
|
||
|
s.erase(std::find_if_not(s.rbegin(),
|
||
|
s.rend(),
|
||
|
isWhitespace).base(),
|
||
|
s.end());
|
||
|
}
|
||
|
|
||
|
static float stripTrailingWhitespace(const std::vector<cocos2d::Node*>& row) {
|
||
|
if (!row.empty()) {
|
||
|
if (auto label = dynamic_cast<Label*>(row.back())) {
|
||
|
const auto width = label->getContentSize().width;
|
||
|
auto str = label->getString();
|
||
|
rtrim(str);
|
||
|
if (label->getString() != str) {
|
||
|
label->setString(str);
|
||
|
return label->getContentSize().width - width;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return 0.0f;
|
||
|
}
|
||
|
|
||
|
static std::string getSubStringOfUTF8String(const std::string& str, std::string::size_type start, std::string::size_type length)
|
||
|
{
|
||
|
std::u32string utf32;
|
||
|
if (!StringUtils::UTF8ToUTF32(str, utf32)) {
|
||
|
CCLOGERROR("Can't convert string to UTF-32: %s", str.c_str());
|
||
|
return "";
|
||
|
}
|
||
|
if (utf32.size() < start) {
|
||
|
CCLOGERROR("'start' is out of range: %ld, %s", static_cast<long>(start), str.c_str());
|
||
|
return "";
|
||
|
}
|
||
|
std::string result;
|
||
|
if (!StringUtils::UTF32ToUTF8(utf32.substr(start, length), result)) {
|
||
|
CCLOGERROR("Can't convert internal UTF-32 string to UTF-8: %s", str.c_str());
|
||
|
return "";
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
class FUIRichElement
|
||
|
{
|
||
|
public:
|
||
|
enum class Type
|
||
|
{
|
||
|
TEXT,
|
||
|
IMAGE,
|
||
|
LINK
|
||
|
};
|
||
|
|
||
|
FUIRichElement(Type type);
|
||
|
virtual ~FUIRichElement() {};
|
||
|
|
||
|
Type _type;
|
||
|
std::string text;
|
||
|
TextFormat textFormat;
|
||
|
int width;
|
||
|
int height;
|
||
|
FUIRichElement* link;
|
||
|
};
|
||
|
|
||
|
FUIRichElement::FUIRichElement(Type type) :
|
||
|
_type(type),
|
||
|
width(0),
|
||
|
height(0),
|
||
|
link(nullptr)
|
||
|
{
|
||
|
};
|
||
|
|
||
|
class FUIXMLVisitor : public SAXDelegator
|
||
|
{
|
||
|
public:
|
||
|
explicit FUIXMLVisitor(FUIRichText* richText);
|
||
|
virtual ~FUIXMLVisitor();
|
||
|
|
||
|
void startElement(void *ctx, const char *name, const char **atts) override;
|
||
|
void endElement(void *ctx, const char *name) override;
|
||
|
void textHandler(void *ctx, const char *s, size_t len) override;
|
||
|
|
||
|
private:
|
||
|
ValueMap tagAttrMapWithXMLElement(const char ** attrs);
|
||
|
int attributeInt(const ValueMap& vm, const std::string& key, int defaultValue);
|
||
|
|
||
|
void pushTextFormat();
|
||
|
void popTextFormat();
|
||
|
void addNewLine(bool check);
|
||
|
void finishTextBlock();
|
||
|
|
||
|
FUIRichText* _richText;
|
||
|
std::vector<TextFormat> _textFormatStack;
|
||
|
std::vector<FUIRichElement*> _linkStack;
|
||
|
TextFormat _format;
|
||
|
size_t _textFormatStackTop;
|
||
|
int _skipText;
|
||
|
bool _ignoreWhiteSpace;
|
||
|
std::string _textBlock;
|
||
|
};
|
||
|
|
||
|
FUIXMLVisitor::FUIXMLVisitor(FUIRichText* richText)
|
||
|
: _richText(richText),
|
||
|
_textFormatStackTop(0),
|
||
|
_skipText(0),
|
||
|
_ignoreWhiteSpace(false)
|
||
|
{
|
||
|
_format = *_richText->_defaultTextFormat;
|
||
|
}
|
||
|
|
||
|
FUIXMLVisitor::~FUIXMLVisitor()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
void FUIXMLVisitor::pushTextFormat()
|
||
|
{
|
||
|
if (_textFormatStack.size() <= _textFormatStackTop)
|
||
|
_textFormatStack.push_back(_format);
|
||
|
else
|
||
|
_textFormatStack[_textFormatStackTop] = _format;
|
||
|
_textFormatStackTop++;
|
||
|
}
|
||
|
|
||
|
void FUIXMLVisitor::popTextFormat()
|
||
|
{
|
||
|
if (_textFormatStackTop > 0)
|
||
|
{
|
||
|
_format = _textFormatStack[_textFormatStackTop - 1];
|
||
|
_textFormatStackTop--;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void FUIXMLVisitor::addNewLine(bool check)
|
||
|
{
|
||
|
FUIRichElement* lastElement = _richText->_richElements.empty() ? nullptr : _richText->_richElements.back();
|
||
|
if (lastElement && lastElement->_type == FUIRichElement::Type::TEXT)
|
||
|
{
|
||
|
if (!check || lastElement->text.back() != '\n')
|
||
|
lastElement->text += "\n";
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
FUIRichElement* element = new FUIRichElement(FUIRichElement::Type::TEXT);
|
||
|
element->textFormat = _format;
|
||
|
element->text = "\n";
|
||
|
_richText->_richElements.push_back(element);
|
||
|
if (!_linkStack.empty())
|
||
|
element->link = _linkStack.back();
|
||
|
}
|
||
|
|
||
|
void FUIXMLVisitor::finishTextBlock()
|
||
|
{
|
||
|
if (!_textBlock.empty())
|
||
|
{
|
||
|
FUIRichElement* element = new FUIRichElement(FUIRichElement::Type::TEXT);
|
||
|
element->textFormat = _format;
|
||
|
element->text = _textBlock;
|
||
|
_textBlock.clear();
|
||
|
_richText->_richElements.push_back(element);
|
||
|
if (!_linkStack.empty())
|
||
|
element->link = _linkStack.back();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#pragma warning(once:4307)
|
||
|
void FUIXMLVisitor::startElement(void* /*ctx*/, const char *elementName, const char **atts)
|
||
|
{
|
||
|
finishTextBlock();
|
||
|
|
||
|
if (strcasecmp(elementName, "b") == 0)
|
||
|
{
|
||
|
pushTextFormat();
|
||
|
_format.bold = true;
|
||
|
}
|
||
|
else if (strcasecmp(elementName, "i") == 0)
|
||
|
{
|
||
|
pushTextFormat();
|
||
|
_format.italics = true;
|
||
|
}
|
||
|
else if (strcasecmp(elementName, "u") == 0)
|
||
|
{
|
||
|
pushTextFormat();
|
||
|
_format.underline = true;
|
||
|
}
|
||
|
else if (strcasecmp(elementName, "font") == 0)
|
||
|
{
|
||
|
pushTextFormat();
|
||
|
ValueMap&& tagAttrValueMap = tagAttrMapWithXMLElement(atts);
|
||
|
_format.fontSize = attributeInt(tagAttrValueMap, "size", _format.fontSize);
|
||
|
|
||
|
auto it = tagAttrValueMap.find("color");
|
||
|
if (it != tagAttrValueMap.end())
|
||
|
{
|
||
|
_format.color = (Color3B)ToolSet::hexToColor(it->second.asString().c_str());
|
||
|
_format._hasColor = true;
|
||
|
}
|
||
|
}
|
||
|
else if (strcasecmp(elementName, "br") == 0)
|
||
|
{
|
||
|
addNewLine(false);
|
||
|
}
|
||
|
else if (strcasecmp(elementName, "img") == 0)
|
||
|
{
|
||
|
std::string src;
|
||
|
ValueMap&& tagAttrValueMap = tagAttrMapWithXMLElement(atts);
|
||
|
|
||
|
int width = 0;
|
||
|
int height = 0;
|
||
|
|
||
|
auto it = tagAttrValueMap.find("src");
|
||
|
if (it != tagAttrValueMap.end()) {
|
||
|
src = it->second.asString();
|
||
|
}
|
||
|
|
||
|
if (!src.empty()) {
|
||
|
PackageItem* pi = UIPackage::getItemByURL(src);
|
||
|
if (pi)
|
||
|
{
|
||
|
width = pi->width;
|
||
|
height = pi->height;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
width = attributeInt(tagAttrValueMap, "width", width);
|
||
|
height = attributeInt(tagAttrValueMap, "height", height);
|
||
|
if (width == 0)
|
||
|
width = 5;
|
||
|
if (height == 0)
|
||
|
height = 10;
|
||
|
|
||
|
FUIRichElement* element = new FUIRichElement(FUIRichElement::Type::IMAGE);
|
||
|
element->width = width;
|
||
|
element->height = height;
|
||
|
element->text = src;
|
||
|
_richText->_richElements.push_back(element);
|
||
|
if (!_linkStack.empty())
|
||
|
element->link = _linkStack.back();
|
||
|
}
|
||
|
else if (strcasecmp(elementName, "a") == 0)
|
||
|
{
|
||
|
pushTextFormat();
|
||
|
|
||
|
std::string href;
|
||
|
ValueMap&& tagAttrValueMap = tagAttrMapWithXMLElement(atts);
|
||
|
auto it = tagAttrValueMap.find("href");
|
||
|
if (it != tagAttrValueMap.end())
|
||
|
href = it->second.asString();
|
||
|
|
||
|
FUIRichElement* element = new FUIRichElement(FUIRichElement::Type::LINK);
|
||
|
element->text = href;
|
||
|
_richText->_richElements.push_back(element);
|
||
|
_linkStack.push_back(element);
|
||
|
|
||
|
if (_richText->_anchorTextUnderline)
|
||
|
_format.underline = true;
|
||
|
if (!_format._hasColor)
|
||
|
_format.color = _richText->_anchorFontColor;
|
||
|
}
|
||
|
else if (strcasecmp(elementName, "p") == 0 || strcasecmp(elementName, "ui") == 0 || strcasecmp(elementName, "div") == 0
|
||
|
|| strcasecmp(elementName, "li") == 0)
|
||
|
{
|
||
|
addNewLine(true);
|
||
|
}
|
||
|
else if (strcasecmp(elementName, "html") == 0 || strcasecmp(elementName, "body") == 0)
|
||
|
{
|
||
|
//full html
|
||
|
_ignoreWhiteSpace = true;
|
||
|
}
|
||
|
else if (strcasecmp(elementName, "head") == 0 || strcasecmp(elementName, "style") == 0 || strcasecmp(elementName, "script") == 0
|
||
|
|| strcasecmp(elementName, "form") == 0)
|
||
|
{
|
||
|
_skipText++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void FUIXMLVisitor::endElement(void* /*ctx*/, const char *elementName)
|
||
|
{
|
||
|
finishTextBlock();
|
||
|
|
||
|
if (strcasecmp(elementName, "b") == 0 || strcasecmp(elementName, "i") == 0 || strcasecmp(elementName, "u") == 0
|
||
|
|| strcasecmp(elementName, "font") == 0)
|
||
|
{
|
||
|
popTextFormat();
|
||
|
}
|
||
|
else if (strcasecmp(elementName, "a") == 0)
|
||
|
{
|
||
|
popTextFormat();
|
||
|
|
||
|
if (!_linkStack.empty())
|
||
|
_linkStack.pop_back();
|
||
|
}
|
||
|
else if (strcasecmp(elementName, "head") == 0 || strcasecmp(elementName, "style") == 0 || strcasecmp(elementName, "script") == 0
|
||
|
|| strcasecmp(elementName, "form") == 0)
|
||
|
{
|
||
|
_skipText--;
|
||
|
}
|
||
|
}
|
||
|
#pragma warning(default:4307)
|
||
|
|
||
|
void FUIXMLVisitor::textHandler(void* /*ctx*/, const char *str, size_t len)
|
||
|
{
|
||
|
if (_skipText != 0)
|
||
|
return;
|
||
|
|
||
|
if (_ignoreWhiteSpace)
|
||
|
{
|
||
|
string s(str, len);
|
||
|
ltrim(s);
|
||
|
rtrim(s);
|
||
|
_textBlock += s;
|
||
|
}
|
||
|
else
|
||
|
_textBlock += string(str, len);
|
||
|
}
|
||
|
|
||
|
ValueMap FUIXMLVisitor::tagAttrMapWithXMLElement(const char ** attrs)
|
||
|
{
|
||
|
ValueMap tagAttrValueMap;
|
||
|
for (const char** attr = attrs; *attr != nullptr; attr = (attrs += 2)) {
|
||
|
if (attr[0] && attr[1]) {
|
||
|
tagAttrValueMap[attr[0]] = attr[1];
|
||
|
}
|
||
|
}
|
||
|
return tagAttrValueMap;
|
||
|
}
|
||
|
|
||
|
int FUIXMLVisitor::attributeInt(const ValueMap& valueMap, const std::string& key, int defaultValue)
|
||
|
{
|
||
|
auto it = valueMap.find(key);
|
||
|
if (it != valueMap.end()) {
|
||
|
string str = it->second.asString();
|
||
|
if (!str.empty() && str.back() == '%')
|
||
|
return ceil(atoi(str.substr(0, str.size() - 1).c_str()) / 100.0f*defaultValue);
|
||
|
else
|
||
|
return atoi(str.c_str());
|
||
|
}
|
||
|
else
|
||
|
return defaultValue;
|
||
|
}
|
||
|
|
||
|
FUIRichText::FUIRichText() :
|
||
|
_formatTextDirty(true),
|
||
|
_textChanged(false),
|
||
|
_leftSpaceWidth(0.0f),
|
||
|
_textRectWidth(0.0f),
|
||
|
_numLines(0),
|
||
|
_overflow(Label::Overflow::NONE),
|
||
|
_anchorTextUnderline(true),
|
||
|
_anchorFontColor(Color3B::BLUE),
|
||
|
_defaultTextFormat(new TextFormat())
|
||
|
{
|
||
|
}
|
||
|
|
||
|
FUIRichText::~FUIRichText()
|
||
|
{
|
||
|
for (auto &it : _richElements)
|
||
|
delete it;
|
||
|
}
|
||
|
|
||
|
bool FUIRichText::init()
|
||
|
{
|
||
|
if (!Node::init())
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void FUIRichText::setDimensions(float width, float height)
|
||
|
{
|
||
|
if ((_numLines > 1 && width != _dimensions.width) || width < _contentSize.width)
|
||
|
_formatTextDirty = true;
|
||
|
_dimensions.setSize(width, height);
|
||
|
}
|
||
|
|
||
|
void FUIRichText::setText(const std::string & value)
|
||
|
{
|
||
|
_formatTextDirty = true;
|
||
|
_textChanged = true;
|
||
|
_text = value;
|
||
|
}
|
||
|
|
||
|
void FUIRichText::applyTextFormat()
|
||
|
{
|
||
|
_textChanged = true;
|
||
|
_formatTextDirty = true;
|
||
|
}
|
||
|
|
||
|
void FUIRichText::setOverflow(cocos2d::Label::Overflow overflow)
|
||
|
{
|
||
|
if (_overflow != overflow)
|
||
|
{
|
||
|
_overflow = overflow;
|
||
|
_formatTextDirty = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const Size & FUIRichText::getContentSize() const
|
||
|
{
|
||
|
if (_formatTextDirty)
|
||
|
const_cast<FUIRichText*>(this)->formatText();
|
||
|
|
||
|
return Node::getContentSize();
|
||
|
}
|
||
|
|
||
|
void FUIRichText::setAnchorTextUnderline(bool enable)
|
||
|
{
|
||
|
if (_anchorTextUnderline != enable)
|
||
|
{
|
||
|
_anchorTextUnderline = enable;
|
||
|
_formatTextDirty = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void FUIRichText::setAnchorFontColor(const cocos2d::Color3B & color)
|
||
|
{
|
||
|
_anchorFontColor = color;
|
||
|
_formatTextDirty = true;
|
||
|
}
|
||
|
|
||
|
const char* FUIRichText::hitTestLink(const cocos2d::Vec2 & worldPoint)
|
||
|
{
|
||
|
Rect rect;
|
||
|
for (auto &child : _children)
|
||
|
{
|
||
|
FUIRichElement* element = (FUIRichElement*)child->getUserData();
|
||
|
if (!element || !element->link)
|
||
|
continue;
|
||
|
|
||
|
rect.size = child->getContentSize();
|
||
|
if (rect.containsPoint(child->convertToNodeSpace(worldPoint)))
|
||
|
return element->link->text.c_str();
|
||
|
}
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
void FUIRichText::visit(cocos2d::Renderer * renderer, const cocos2d::Mat4 & parentTransform, uint32_t parentFlags)
|
||
|
{
|
||
|
if (_visible)
|
||
|
formatText();
|
||
|
|
||
|
Node::visit(renderer, parentTransform, parentFlags);
|
||
|
}
|
||
|
|
||
|
void FUIRichText::formatText()
|
||
|
{
|
||
|
if (!_formatTextDirty)
|
||
|
return;
|
||
|
|
||
|
if (_textChanged)
|
||
|
{
|
||
|
_textChanged = false;
|
||
|
_richElements.clear();
|
||
|
_numLines = 0;
|
||
|
|
||
|
if (!_text.empty())
|
||
|
{
|
||
|
string xmlText = "<dummy>" + _text + "</dummy>";
|
||
|
FUIXMLVisitor visitor(this);
|
||
|
SAXParser parser;
|
||
|
parser.setDelegator(&visitor);
|
||
|
parser.parseIntrusive(&xmlText.front(), xmlText.length());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
removeAllChildrenWithCleanup(true);
|
||
|
_elementRenders.clear();
|
||
|
_imageLoaders.clear();
|
||
|
if (_overflow == Label::Overflow::NONE)
|
||
|
_textRectWidth = FLT_MAX;
|
||
|
else
|
||
|
_textRectWidth = _dimensions.width - GUTTER_X * 2;
|
||
|
|
||
|
int size = (int)_richElements.size();
|
||
|
if (size == 0)
|
||
|
{
|
||
|
formarRenderers();
|
||
|
_formatTextDirty = false;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
addNewLine();
|
||
|
for (int i = 0; i < size; ++i)
|
||
|
{
|
||
|
FUIRichElement* element = static_cast<FUIRichElement*>(_richElements.at(i));
|
||
|
switch (element->_type)
|
||
|
{
|
||
|
case FUIRichElement::Type::TEXT:
|
||
|
{
|
||
|
FastSplitter fs;
|
||
|
fs.start(element->text.c_str(), (int)element->text.size(), '\n');
|
||
|
bool first = true;
|
||
|
while (fs.next())
|
||
|
{
|
||
|
if (!first)
|
||
|
addNewLine();
|
||
|
if (fs.getTextLength() > 0)
|
||
|
handleTextRenderer(element, element->textFormat, string(fs.getText(), fs.getTextLength()));
|
||
|
first = false;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case FUIRichElement::Type::IMAGE:
|
||
|
handleImageRenderer(element);
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
formarRenderers();
|
||
|
_formatTextDirty = false;
|
||
|
}
|
||
|
|
||
|
void FUIRichText::addNewLine()
|
||
|
{
|
||
|
_leftSpaceWidth = _textRectWidth;
|
||
|
_elementRenders.emplace_back();
|
||
|
_numLines++;
|
||
|
}
|
||
|
|
||
|
void FUIRichText::handleTextRenderer(FUIRichElement* element, const TextFormat& format, const std::string& text)
|
||
|
{
|
||
|
FUILabel* textRenderer = FUILabel::create();
|
||
|
textRenderer->setCascadeOpacityEnabled(true);
|
||
|
textRenderer->getTextFormat()->setFormat(format);
|
||
|
textRenderer->applyTextFormat();
|
||
|
textRenderer->setString(text);
|
||
|
textRenderer->setUserData(element);
|
||
|
|
||
|
float textRendererWidth = textRenderer->getContentSize().width;
|
||
|
_leftSpaceWidth -= textRendererWidth;
|
||
|
if (_leftSpaceWidth >= 0)
|
||
|
{
|
||
|
_elementRenders.back().push_back(textRenderer);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
int leftLength = findSplitPositionForWord(textRenderer, text);
|
||
|
|
||
|
//The minimum cut length is 1, otherwise will cause the infinite loop.
|
||
|
if (0 == leftLength) leftLength = 1;
|
||
|
std::string leftWords = getSubStringOfUTF8String(text, 0, leftLength);
|
||
|
int rightStart = leftLength;
|
||
|
if (std::isspace(text[rightStart], std::locale()))
|
||
|
rightStart++;
|
||
|
std::string cutWords = getSubStringOfUTF8String(text, rightStart, text.length() - leftLength);
|
||
|
if (leftLength > 0)
|
||
|
{
|
||
|
FUILabel* leftRenderer = FUILabel::create();
|
||
|
leftRenderer->setCascadeOpacityEnabled(true);
|
||
|
leftRenderer->getTextFormat()->setFormat(format);
|
||
|
leftRenderer->applyTextFormat();
|
||
|
leftRenderer->setString(getSubStringOfUTF8String(leftWords, 0, leftLength));
|
||
|
leftRenderer->setUserData(element);
|
||
|
_elementRenders.back().push_back(leftRenderer);
|
||
|
}
|
||
|
|
||
|
if (cutWords.length() > 0)
|
||
|
{
|
||
|
addNewLine();
|
||
|
handleTextRenderer(element, format, cutWords);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int FUIRichText::findSplitPositionForWord(cocos2d::Label* label, const std::string& text)
|
||
|
{
|
||
|
auto originalLeftSpaceWidth = _leftSpaceWidth + label->getContentSize().width;
|
||
|
|
||
|
bool startingNewLine = (_textRectWidth == originalLeftSpaceWidth);
|
||
|
if (!isWrappable(text))
|
||
|
{
|
||
|
if (startingNewLine)
|
||
|
return (int)text.length();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
for (int idx = (int)text.size() - 1; idx >= 0; )
|
||
|
{
|
||
|
int newidx = getPrevWord(text, idx);
|
||
|
if (newidx >= 0)
|
||
|
{
|
||
|
idx = newidx;
|
||
|
auto leftStr = getSubStringOfUTF8String(text, 0, idx);
|
||
|
label->setString(leftStr);
|
||
|
if (label->getContentSize().width <= originalLeftSpaceWidth)
|
||
|
return idx;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (startingNewLine)
|
||
|
return idx;
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// no spaces... return the original label + size
|
||
|
label->setString(text);
|
||
|
return (int)text.size();
|
||
|
}
|
||
|
|
||
|
void FUIRichText::handleImageRenderer(FUIRichElement* element)
|
||
|
{
|
||
|
GLoader* loader = GLoader::create();
|
||
|
_imageLoaders.pushBack(loader);
|
||
|
loader->setSize(element->width, element->height);
|
||
|
loader->setFill(LoaderFillType::SCALE_FREE);
|
||
|
loader->setURL(element->text);
|
||
|
loader->displayObject()->setUserData(element);
|
||
|
|
||
|
_leftSpaceWidth -= (element->width + 4);
|
||
|
if (_leftSpaceWidth < 0.0f)
|
||
|
{
|
||
|
addNewLine();
|
||
|
_elementRenders.back().push_back(loader->displayObject());
|
||
|
_leftSpaceWidth -= (element->width + 4);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_elementRenders.back().push_back(loader->displayObject());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void FUIRichText::formarRenderers()
|
||
|
{
|
||
|
float nextPosY = GUTTER_Y;
|
||
|
float textWidth = 0;
|
||
|
float textHeight = 0;
|
||
|
for (auto& row : _elementRenders)
|
||
|
{
|
||
|
if (nextPosY != GUTTER_Y)
|
||
|
nextPosY += _defaultTextFormat->lineSpacing - 3;
|
||
|
|
||
|
float nextPosX = GUTTER_X;
|
||
|
float lineHeight = 0.0f;
|
||
|
float lineTextHeight = 0.0f;
|
||
|
for (auto& node : row)
|
||
|
{
|
||
|
lineHeight = MAX(node->getContentSize().height, lineHeight);
|
||
|
if (((FUIRichElement*)node->getUserData())->_type == FUIRichElement::Type::TEXT)
|
||
|
lineTextHeight = MAX(node->getContentSize().height, lineTextHeight);
|
||
|
}
|
||
|
|
||
|
nextPosY += lineHeight;
|
||
|
|
||
|
for (auto& node : row)
|
||
|
{
|
||
|
node->setAnchorPoint(Vec2::ZERO);
|
||
|
int adjustment = 0;
|
||
|
if (((FUIRichElement*)node->getUserData())->_type == FUIRichElement::Type::IMAGE)
|
||
|
{
|
||
|
nextPosX += 2;
|
||
|
adjustment = floor((lineHeight - node->getContentSize().height) / 2);
|
||
|
}
|
||
|
else //text
|
||
|
{
|
||
|
adjustment = floor((lineHeight - lineTextHeight) / 2);
|
||
|
}
|
||
|
node->setPosition(nextPosX, _dimensions.height - nextPosY + adjustment);
|
||
|
this->addChild(node, 1);
|
||
|
nextPosX += node->getContentSize().width;
|
||
|
if (((FUIRichElement*)node->getUserData())->_type == FUIRichElement::Type::IMAGE)
|
||
|
nextPosX += 2;
|
||
|
}
|
||
|
nextPosX += GUTTER_X;
|
||
|
if (nextPosX > textWidth)
|
||
|
textWidth = nextPosX;
|
||
|
|
||
|
if (_overflow != Label::Overflow::NONE)
|
||
|
doHorizontalAlignment(row, nextPosX);
|
||
|
}
|
||
|
if (textWidth == GUTTER_X + GUTTER_X)
|
||
|
textWidth = 0;
|
||
|
else if (_numLines > 1 || (_defaultTextFormat->align != TextHAlignment::LEFT && _overflow != Label::Overflow::NONE))
|
||
|
textWidth = MAX(_dimensions.width, textWidth);
|
||
|
if (nextPosY != GUTTER_Y)
|
||
|
textHeight = nextPosY + GUTTER_Y;
|
||
|
else
|
||
|
textHeight = 0;
|
||
|
setContentSize(Size(textWidth, textHeight));
|
||
|
|
||
|
float oldDimensionsHeight = _dimensions.height;
|
||
|
if (_overflow == Label::Overflow::NONE)
|
||
|
_dimensions = _contentSize;
|
||
|
else if (_overflow == Label::Overflow::RESIZE_HEIGHT)
|
||
|
_dimensions.height = _contentSize.height;
|
||
|
float delta = _contentSize.height - oldDimensionsHeight;
|
||
|
if (_defaultTextFormat->verticalAlign == TextVAlignment::CENTER)
|
||
|
delta -= floor((_dimensions.height - textHeight) * 0.5f);
|
||
|
else if (_defaultTextFormat->verticalAlign == TextVAlignment::BOTTOM)
|
||
|
delta -= _dimensions.height - textHeight;
|
||
|
if (delta != 0)
|
||
|
{
|
||
|
Vec2 offset(0, delta);
|
||
|
for (auto& row : _elementRenders)
|
||
|
{
|
||
|
for (auto& node : row)
|
||
|
{
|
||
|
node->setPosition(node->getPosition() + offset);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_elementRenders.clear();
|
||
|
}
|
||
|
|
||
|
void FUIRichText::doHorizontalAlignment(const std::vector<cocos2d::Node*> &row, float rowWidth) {
|
||
|
if (_defaultTextFormat->align != TextHAlignment::LEFT) {
|
||
|
const auto diff = stripTrailingWhitespace(row);
|
||
|
const auto leftOver = _dimensions.width - (rowWidth + diff);
|
||
|
const float leftPadding = getPaddingAmount(_defaultTextFormat->align, leftOver);
|
||
|
const Vec2 offset(leftPadding, 0.f);
|
||
|
for (auto& node : row) {
|
||
|
node->setPosition(node->getPosition() + offset);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
NS_FGUI_END
|