mirror of
https://github.com/smallmain/cocos-enhance-kit.git
synced 2024-12-26 11:48:29 +00:00
921 lines
31 KiB
C++
921 lines
31 KiB
C++
/****************************************************************************
|
|
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
|
|
|
|
http://www.cocos.com
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
****************************************************************************/
|
|
#include "2d/CCLabelLayout.h"
|
|
#include "2d/CCTTFLabelAtlasCache.h"
|
|
#include "2d/CCTTFLabelRenderer.h"
|
|
|
|
#include "base/ccUTF8.h"
|
|
#include "MiddlewareMacro.h"
|
|
#include "renderer/renderer/Pass.h"
|
|
#include "renderer/renderer/Effect.h"
|
|
#include "renderer/renderer/Technique.h"
|
|
#include "renderer/scene/assembler/CustomAssembler.hpp"
|
|
#include "cocos/editor-support/MeshBuffer.h"
|
|
#include "cocos/editor-support/IOBuffer.h"
|
|
#include "cocos/editor-support/middleware-adapter.h"
|
|
#include "cocos/editor-support/MiddlewareManager.h"
|
|
|
|
#include "renderer/gfx/DeviceGraphics.h"
|
|
#include "renderer/gfx/Texture2D.h"
|
|
|
|
#include <cassert>
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
#include <unordered_map>
|
|
#include <chrono>
|
|
|
|
#include "base/ccConfig.h"
|
|
|
|
#if CC_ENABLE_TTF_LABEL_RENDERER
|
|
|
|
#define INIT_VERTEX_SIZE 32
|
|
|
|
namespace {
|
|
const std::string textureKey = "texture";
|
|
const std::string shadowColor = "shadowColor";
|
|
const std::string outlineSizeKey = "outlineSize";
|
|
const std::string outlineColorKey= "outlineColor";
|
|
}
|
|
|
|
using namespace cocos2d::renderer;
|
|
using namespace cocos2d::middleware;
|
|
|
|
namespace cocos2d {
|
|
|
|
namespace {
|
|
void recalculateUV(const Rect &oldRect, const Rect &oldUV, const Rect &newRect, Rect &newUV)
|
|
{
|
|
auto offsetOrigin = newRect.origin - oldRect.origin;
|
|
float uvWidth = newRect.size.width / oldRect.size.width * oldUV.size.width;
|
|
float uvHeight = newRect.size.height / oldRect.size.height * oldUV.size.height;
|
|
float uvOriginX = offsetOrigin.x / oldRect.size.width * oldUV.size.width + oldUV.origin.x;
|
|
float uvOriginY = (oldUV.size.height - uvHeight - offsetOrigin.y / oldRect.size.height * oldUV.size.height) + oldUV.origin.y;
|
|
newUV.setRect(uvOriginX, uvOriginY, uvWidth, uvHeight);
|
|
}
|
|
|
|
inline void find_2nd_3rd(float min1, float max1, float min2, float max2, float &second, float &third)
|
|
{
|
|
assert(max1 >= min1 && max2 >= min2);
|
|
|
|
if (max1 < max2)
|
|
{
|
|
second = min1 < min2 ? min2 : min1;
|
|
third = max1;
|
|
}
|
|
else
|
|
{
|
|
second = min2 < min1 ? min1 : min2;
|
|
third = max2;
|
|
}
|
|
}
|
|
|
|
void rectUnion(const Rect &a, const Rect &b, Rect &out)
|
|
{
|
|
float xMax, yMax;
|
|
find_2nd_3rd(a.getMinX(), a.getMaxX(), b.getMinX(), b.getMaxX(), out.origin.x, xMax);
|
|
find_2nd_3rd(a.getMinY(), a.getMaxY(), b.getMinY(), b.getMaxY(), out.origin.y, yMax);
|
|
out.size.setSize(xMax - out.origin.x, yMax - out.origin.y);
|
|
}
|
|
|
|
void subRect(const Rect &rect, const Rect &uv, float offsetX, float width, Rect &out1, Rect &out2)
|
|
{
|
|
out1.origin.set(offsetX + rect.origin.x, rect.origin.y);
|
|
out1.size.setSize(width, rect.size.height);
|
|
out2.origin.set(uv.origin.x + uv.size.width * offsetX / rect.size.width, uv.origin.y);
|
|
out2.size.setSize(uv.size.width * width / rect.size.width, uv.size.height);
|
|
}
|
|
|
|
void splitRectIntoThreeParts(int texId, const Rect &defRect, const Rect &targetRect, const Rect &uv, cocos2d::TextRowSpace &space)
|
|
{
|
|
constexpr float LEFT = 0.3f;
|
|
constexpr float MIDDLE = 0.4f;
|
|
constexpr float RIGHT = 0.3f;
|
|
Rect out1, out2;
|
|
float cursorX = 0.0f;
|
|
//part left
|
|
subRect(targetRect, uv, 0.0f, targetRect.size.width * LEFT, out1, out2);
|
|
cursorX = defRect.size.width *LEFT;
|
|
out1.size.width = cursorX;
|
|
space.fillRect(texId, out1, out2);
|
|
//part middle
|
|
subRect(targetRect, uv, targetRect.size.width * LEFT, targetRect.size.width * MIDDLE, out1, out2);
|
|
out1.origin.x = targetRect.origin.x + cursorX;
|
|
out1.size.width = targetRect.size.width - defRect.size.width * (LEFT + RIGHT);
|
|
cursorX = targetRect.size.width - defRect.size.width * RIGHT;
|
|
space.fillRect(texId, out1, out2);
|
|
//part right
|
|
subRect(targetRect, uv, targetRect.size.width * (LEFT + MIDDLE) , targetRect.size.width *RIGHT, out1, out2);
|
|
out1.origin.x = targetRect.origin.x + cursorX;
|
|
out1.size.width = defRect.size.width * RIGHT;
|
|
space.fillRect(texId, out1, out2);
|
|
}
|
|
}
|
|
|
|
class TextRenderGroupItem {
|
|
public:
|
|
|
|
enum DirtyFlag {
|
|
VERTEX_DIRTY = 1,
|
|
INDEXES_DIRTY = 2
|
|
};
|
|
|
|
TextRenderGroupItem(renderer::Texture *);
|
|
virtual ~TextRenderGroupItem();
|
|
|
|
void reset();
|
|
|
|
void addRect(const Rect &vert, const Rect &uv, const Color4B &color, bool italics);
|
|
|
|
void upload();
|
|
|
|
inline int getRectSize() const { return _rectSize; }
|
|
|
|
middleware::MeshBuffer* getBuffer() { return _buffer; }
|
|
|
|
private:
|
|
|
|
void addIndexes();
|
|
|
|
middleware::MeshBuffer * _buffer = nullptr;
|
|
renderer::Texture *_texture = nullptr;
|
|
int _rectSize = 0;
|
|
int _indexSize = 0;
|
|
|
|
int _dirtyFlags = -1;
|
|
};
|
|
|
|
|
|
class TextRenderGroup {
|
|
public:
|
|
|
|
void reset();
|
|
void addRect(renderer::Texture*, const Rect &vert, const Rect &uv, const Color4B &color, bool italics);
|
|
|
|
int fill(CustomAssembler *assembler, int, LabelLayout *layout, EffectVariant *);
|
|
|
|
private:
|
|
std::unordered_map<renderer::Texture*, std::shared_ptr<TextRenderGroupItem>> _buffers;
|
|
};
|
|
|
|
|
|
TextRenderGroupItem::TextRenderGroupItem(renderer::Texture* tex)
|
|
{
|
|
_texture = tex;
|
|
_buffer = new middleware::MeshBuffer(VF_XYUVC, sizeof(uint16_t) * 6 * INIT_VERTEX_SIZE, INIT_VERTEX_SIZE * 4);
|
|
}
|
|
TextRenderGroupItem::~TextRenderGroupItem()
|
|
{
|
|
delete _buffer;
|
|
}
|
|
|
|
void TextRenderGroupItem::reset() {
|
|
|
|
_buffer->reset();
|
|
_rectSize = 0;
|
|
_dirtyFlags = -1;
|
|
}
|
|
|
|
void TextRenderGroupItem::addRect(const Rect &rect, const Rect &uv, const Color4B &color, bool italics)
|
|
{
|
|
middleware::IOBuffer &vertexBuffer = _buffer->getVB();
|
|
const int minSize = sizeof(V2F_T2F_C4B) * 4;
|
|
vertexBuffer.checkSpace(minSize << 1, true);
|
|
|
|
V2F_T2F_C4B *verts = ((V2F_T2F_C4B*)vertexBuffer.getBuffer()) + 4 * _rectSize;
|
|
|
|
float height = rect.size.height;
|
|
float offsetX = 0.0f;
|
|
const float factor = 0.21255f;
|
|
if (italics) {
|
|
offsetX = height * factor;
|
|
}
|
|
|
|
verts[0].vertex = { rect.getMinX() + offsetX, rect.getMaxY() };
|
|
verts[1].vertex = { rect.getMaxX() + offsetX, rect.getMaxY() };
|
|
verts[2].vertex = { rect.getMinX() - offsetX, rect.getMinY() };
|
|
verts[3].vertex = { rect.getMaxX() - offsetX, rect.getMinY() };
|
|
|
|
verts[0].texCoord = { uv.getMinX(), uv.getMinY() };
|
|
verts[1].texCoord = { uv.getMaxX(), uv.getMinY() };
|
|
verts[2].texCoord = { uv.getMinX(), uv.getMaxY() };
|
|
verts[3].texCoord = { uv.getMaxX(), uv.getMaxY() };
|
|
|
|
verts[0].color = color;
|
|
verts[1].color = color;
|
|
verts[2].color = color;
|
|
verts[3].color = color;
|
|
|
|
vertexBuffer.move(4 * sizeof(V2F_T2F_C4B));
|
|
_rectSize += 1;
|
|
|
|
_dirtyFlags |= DirtyFlag::VERTEX_DIRTY;
|
|
}
|
|
|
|
void TextRenderGroupItem::addIndexes()
|
|
{
|
|
middleware::IOBuffer &indexBuffer = _buffer->getIB();
|
|
int indexSize = sizeof(uint16_t) * 6 * (_rectSize - _indexSize);
|
|
indexBuffer.checkSpace(indexSize << 1, true);
|
|
uint16_t *indices = (uint16_t*)indexBuffer.getBuffer();
|
|
for (int i = _indexSize; i < _rectSize; i += 1)
|
|
{
|
|
indices[i * 6 + 0] = 0 + 4 * i;
|
|
indices[i * 6 + 1] = 1 + 4 * i;
|
|
indices[i * 6 + 2] = 2 + 4 * i;
|
|
indices[i * 6 + 3] = 1 + 4 * i;
|
|
indices[i * 6 + 4] = 3 + 4 * i;
|
|
indices[i * 6 + 5] = 2 + 4 * i;
|
|
}
|
|
|
|
indexBuffer.move(sizeof(uint16_t) * 6 * (_rectSize - _indexSize));
|
|
if (_indexSize < _rectSize)
|
|
{
|
|
_indexSize = _rectSize;
|
|
_dirtyFlags |= DirtyFlag::INDEXES_DIRTY;
|
|
}
|
|
}
|
|
|
|
void TextRenderGroupItem::upload()
|
|
{
|
|
addIndexes();
|
|
|
|
middleware::IOBuffer &vertexBuffer = _buffer->getVB();
|
|
middleware::IOBuffer &indexBuffer = _buffer->getIB();
|
|
|
|
vertexBuffer.move(_rectSize * 4 * sizeof(V2F_T2F_C4B));
|
|
indexBuffer.move(_rectSize * 6 * sizeof(uint16_t));
|
|
|
|
if (_dirtyFlags | DirtyFlag::INDEXES_DIRTY)
|
|
{
|
|
_buffer->uploadIB();
|
|
}
|
|
|
|
if (_dirtyFlags | DirtyFlag::VERTEX_DIRTY)
|
|
{
|
|
_buffer->uploadVB();
|
|
}
|
|
|
|
_dirtyFlags = 0;
|
|
}
|
|
|
|
void TextRenderGroup::addRect(renderer::Texture* tex, const Rect &vert, const Rect &uv, const Color4B &color, bool italics)
|
|
{
|
|
auto &itemPtr = _buffers[tex];
|
|
if (!itemPtr) {
|
|
itemPtr = std::make_shared<TextRenderGroupItem>(tex);
|
|
}
|
|
itemPtr->addRect(vert, uv, color, italics);
|
|
}
|
|
|
|
|
|
void TextRenderGroup::reset()
|
|
{
|
|
for (auto &it : _buffers)
|
|
{
|
|
it.second->reset();
|
|
}
|
|
_buffers.clear();
|
|
}
|
|
|
|
int TextRenderGroup::fill(CustomAssembler *assembler, int startIndex, LabelLayout *layout, EffectVariant *templateEffect)
|
|
{
|
|
int groupIndex = startIndex;
|
|
for (auto &it : _buffers)
|
|
{
|
|
auto &item = it.second;
|
|
if (item->getRectSize() > 0)
|
|
{
|
|
item->upload();
|
|
|
|
assembler->updateIABuffer(groupIndex, item->getBuffer()->getGLVB(), item->getBuffer()->getGLIB());
|
|
|
|
assembler->updateIARange(groupIndex, 0, item->getRectSize() * 6);
|
|
|
|
auto *effect = assembler->getEffect(groupIndex);
|
|
|
|
if (!effect && templateEffect) {
|
|
effect = new cocos2d::renderer::EffectVariant();
|
|
effect->autorelease();
|
|
effect->copy(templateEffect);
|
|
assembler->updateEffect(groupIndex, effect);
|
|
}
|
|
|
|
if(effect->getPasses().at(0)->getDefinesHash() != templateEffect->getPasses().at(0)->getDefinesHash()){
|
|
effect->copy(templateEffect);
|
|
}
|
|
|
|
if (effect) {
|
|
effect->setProperty(textureKey, it.first);
|
|
}
|
|
|
|
groupIndex += 1;
|
|
}
|
|
}
|
|
return groupIndex;
|
|
}
|
|
|
|
|
|
|
|
struct TextSpaceArray {
|
|
|
|
void addSpace(TextRowSpace& space)
|
|
{
|
|
if (space.size() > 0)
|
|
{
|
|
_maxWidth = std::max(_maxWidth, space.getWidth());
|
|
}
|
|
_data.emplace_back(std::move(space));
|
|
}
|
|
|
|
float _maxWidth = FLT_MIN;
|
|
std::vector<TextRowSpace> _data;
|
|
};
|
|
|
|
|
|
TextRowSpace::GlyphBlock & TextRowSpace::operator[](size_t i)
|
|
{
|
|
return _data[i];
|
|
}
|
|
|
|
|
|
TextRowSpace::GlyphBlock & TextRowSpace::appendBlock()
|
|
{
|
|
_data.resize(_data.size() + 1);
|
|
return _data[_data.size() - 1];
|
|
}
|
|
|
|
TextRowSpace::TextRowSpace(TextRowSpace &&other)
|
|
{
|
|
_left = other._left;
|
|
_bottom = other._bottom;
|
|
_right = other._right;
|
|
_top = other._top;
|
|
_x = other._x;
|
|
_y = other._y;
|
|
_data = std::move(other._data);
|
|
_ignored = other._ignored;
|
|
other.reset();
|
|
}
|
|
|
|
|
|
void TextRowSpace::reset()
|
|
{
|
|
_left = FLT_MAX;
|
|
_bottom = FLT_MAX;
|
|
_right = FLT_MIN;
|
|
_top = FLT_MIN;
|
|
_x = 0.0f;
|
|
_y = 0.0f;
|
|
_data.clear();
|
|
_ignored = false;
|
|
}
|
|
|
|
void TextRowSpace::fillRect(int texId, Rect &rect, Rect &uv)
|
|
{
|
|
GlyphBlock &block = appendBlock();
|
|
_left = std::min(_left, rect.getMinX());
|
|
_right = std::max(_right, rect.getMaxX());
|
|
_bottom = std::min(_bottom, rect.getMinY());
|
|
_top = std::max(_top, rect.getMaxY());
|
|
block.area = rect;
|
|
block.uv = uv;
|
|
block.ignored = false;
|
|
block.texId = texId;
|
|
}
|
|
|
|
void TextRowSpace::translate(float x, float y)
|
|
{
|
|
_x += x;
|
|
_y += y;
|
|
}
|
|
|
|
Vec2 TextRowSpace::center() const
|
|
{
|
|
Vec2 ret((_left + _right) / 2.0f, (_bottom + _top) / 2.0f);
|
|
return ret;
|
|
}
|
|
|
|
float TextRowSpace::getExtentedWidth(float left, float right) const
|
|
{
|
|
if (_data.size() == 0)
|
|
{
|
|
return right - left;
|
|
}
|
|
return std::max(_right, right) - std::min(_left, left);
|
|
}
|
|
|
|
|
|
void TextRowSpace::clip(const Rect &rect)
|
|
{
|
|
Rect contentRect(_x + _left, _y + _bottom, _right - _left, _top - _bottom);
|
|
|
|
if (!rect.intersectsRect(contentRect)) {
|
|
_ignored = true;
|
|
return;
|
|
}
|
|
|
|
const auto size = _data.size();
|
|
const auto offset = getOffset();
|
|
Rect letter;
|
|
Rect unionRect;
|
|
for (int i = 0; i < size; i++)
|
|
{
|
|
letter.size = _data[i].area.size;
|
|
letter.origin = offset + _data[i].area.origin;
|
|
|
|
if (!rect.intersectsRect(letter))
|
|
{
|
|
_data[i].ignored = true;
|
|
}
|
|
else if (rect.containsPoint(letter.origin) && rect.containsPoint(Vec2(letter.getMaxX(), letter.getMaxY())))
|
|
{
|
|
_data[i].area.origin = letter.origin;
|
|
}
|
|
else
|
|
{
|
|
//Rect unionRect = letter.unionWithRect(rect); // incorrect result
|
|
rectUnion(letter, rect, unionRect);
|
|
_data[i].area = unionRect;
|
|
recalculateUV(letter, _data[i].uv, unionRect, _data[i].uv);
|
|
}
|
|
|
|
}
|
|
//ignore translate
|
|
_x = 0.0f;
|
|
_y = 0.0f;
|
|
}
|
|
|
|
Rect TextRowSpace::asRect() const {
|
|
return Rect(_left + _x, _bottom + _y, _right - _left, _top - _bottom);
|
|
}
|
|
|
|
bool LabelLayout::init(const std::string& font, const std::string& text, float fontSize, float retinaFontSize, LabelLayoutInfo *info)
|
|
{
|
|
_inited = true;
|
|
_layoutInfo = info;
|
|
_retinaFontSize = std::max(fontSize, retinaFontSize);
|
|
_fontAtlas = TTFLabelAtlasCache::getInstance()->load(font, _retinaFontSize, info);
|
|
if(!_fontAtlas) {
|
|
return false;
|
|
}
|
|
_fontScale = fontSize / _fontAtlas->getFontSize();
|
|
_groups = std::make_shared<TextRenderGroup>();
|
|
if (info->shadowBlur >= 0)
|
|
{
|
|
_shadowGroups = std::make_shared<TextRenderGroup>();
|
|
}
|
|
_string = text;
|
|
_font = font;
|
|
_fontSize = fontSize;
|
|
cocos2d::StringUtils::UTF8ToUTF32(text.c_str(), _u32string);
|
|
|
|
_textSpace.clear();
|
|
|
|
updateContent();
|
|
return true;
|
|
}
|
|
|
|
LabelLayout::~LabelLayout()
|
|
{
|
|
}
|
|
|
|
|
|
void LabelLayout::setString(const std::string &txt, bool forceUpdate)
|
|
{
|
|
if (_string != txt) {
|
|
_string = txt;
|
|
cocos2d::StringUtils::UTF8ToUTF32(txt.c_str(), _u32string);
|
|
updateContent();
|
|
}
|
|
else if (forceUpdate)
|
|
{
|
|
updateContent();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
bool LabelLayout::updateContent()
|
|
{
|
|
if(!_fontAtlas)
|
|
{
|
|
return false;
|
|
}
|
|
auto *atlas = _fontAtlas->getFontAtlas();
|
|
auto *ttf = _fontAtlas->getTTF();
|
|
|
|
FontLetterDefinition* letterDef = nullptr;
|
|
Rect row;
|
|
|
|
|
|
const float LineHeight = _layoutInfo->lineHeight;
|
|
const float SpaceX = _layoutInfo->spaceX;
|
|
const LabelOverflow OverFlow = _layoutInfo->overflow;
|
|
const bool ClampAndWrap = _layoutInfo->wrap && OverFlow == LabelOverflow::CLAMP;
|
|
const bool ResizeHeight = OverFlow == LabelOverflow::RESIZE_HEIGHT;
|
|
const int ContentWidth = _layoutInfo->width;
|
|
const int ContentHeight = _layoutInfo->height;
|
|
const LabelAlignmentH HAlign = _layoutInfo->halign;
|
|
const LabelAlignmentV VAlign = _layoutInfo->valign;
|
|
const float AnchorX = _layoutInfo->anchorX;
|
|
const float AnchorY = _layoutInfo->anchorY;
|
|
const bool Underline = _layoutInfo->underline;
|
|
|
|
//LabelCursor cursor;
|
|
float cursorX = 0;
|
|
|
|
TextSpaceArray textSpaces;
|
|
TextRowSpace rowSpace;
|
|
|
|
Rect letterRect;
|
|
|
|
std::unique_ptr<std::vector<int> > kerning = nullptr;
|
|
if (_enableKerning) {
|
|
kerning = ttf->getHorizontalKerningForUTF32Text(_u32string);
|
|
}
|
|
|
|
|
|
atlas->prepareLetters(_u32string, ttf);
|
|
|
|
for (int i = 0; i < _u32string.size(); i++)
|
|
{
|
|
auto ch = _u32string[i];
|
|
|
|
if (ch == u'\r')
|
|
{
|
|
cursorX = 0;
|
|
continue;
|
|
}
|
|
|
|
if (ch == u'\n')
|
|
{
|
|
textSpaces.addSpace(rowSpace);
|
|
cursorX = 0;
|
|
continue;
|
|
}
|
|
|
|
letterDef = atlas->getOrLoad(ch, ttf);
|
|
if (!letterDef) continue;
|
|
|
|
letterRect = letterDef->rect;
|
|
letterRect.origin *= _fontScale;
|
|
letterRect.size = Size(letterRect.size.width * _fontScale, letterRect.size.height * _fontScale);
|
|
|
|
if (_enableKerning && kerning) {
|
|
auto const kerningX = kerning->at(i) * _fontScale;
|
|
cursorX += kerningX;
|
|
}
|
|
|
|
float left = cursorX - letterDef->outline * _fontScale + letterRect.getMinX();
|
|
float bottom = letterDef->outline * _fontScale - letterRect.getMaxY();
|
|
|
|
if ((ResizeHeight || ClampAndWrap) && rowSpace.getExtentedWidth(left, left + letterRect.size.width) > ContentWidth)
|
|
{
|
|
// RESIZE_HEIGHT or (CLAMP & Enable Wrap)
|
|
textSpaces.addSpace(rowSpace);
|
|
cursorX = 0;
|
|
left = cursorX - letterDef->outline * _fontScale + letterRect.getMinX();
|
|
}
|
|
|
|
float width = letterRect.size.width;
|
|
float height = letterRect.size.height;
|
|
|
|
Rect letterRectInline(left, bottom, width, height);
|
|
Rect letterTexture(letterDef->texX, letterDef->texY, letterDef->texWidth, letterDef->texHeight);
|
|
|
|
rowSpace.fillRect(letterDef->textureID, letterRectInline, letterTexture);
|
|
|
|
cursorX += SpaceX + letterDef->xAdvance * _fontScale;
|
|
}
|
|
|
|
textSpaces.addSpace(rowSpace);
|
|
|
|
auto& list = textSpaces._data;
|
|
|
|
//add underline
|
|
if(Underline){
|
|
auto underline = atlas->getOrLoad('_', ttf);
|
|
|
|
for (auto &space : list) {
|
|
Rect letterRect = underline->rect;
|
|
letterRect.origin *= _fontScale;
|
|
letterRect.size = Size(letterRect.size.width * _fontScale, letterRect.size.height * _fontScale);
|
|
float bottom = underline->outline * _fontScale - letterRect.getMaxY();
|
|
float left = -letterDef->outline * _fontScale + space.getLeft();
|
|
|
|
Rect letterRectInline(left, bottom, space.getWidth(), letterRect.size.height);
|
|
Rect letterTexture(underline->texX, underline->texY, underline->texWidth, underline->texHeight);
|
|
splitRectIntoThreeParts(underline->textureID, letterRect, letterRectInline, letterTexture, space);
|
|
}
|
|
}
|
|
|
|
Vec2 newCenter;
|
|
Vec2 delta;
|
|
// default value in LabelOverflow::None mode
|
|
float maxLineWidth = textSpaces._maxWidth;
|
|
const float TotalTextHeight = textSpaces._data.size() * LineHeight;
|
|
float topMost = (TotalTextHeight - LineHeight) * (1.0f - AnchorY);
|
|
float leftMost = - AnchorX * maxLineWidth;
|
|
float rightMost = (1.0f - AnchorX) * maxLineWidth;
|
|
float centerX = (0.5f - AnchorX) * ContentWidth;
|
|
|
|
Rect textArea(0, 0, 0, 0);
|
|
|
|
for (int i = 0; i < list.size(); i++)
|
|
{
|
|
auto& s = list[i];
|
|
if (!s.validate())
|
|
{
|
|
continue;
|
|
}
|
|
if (HAlign == LabelAlignmentH::CENTER)
|
|
{
|
|
newCenter.set(centerX, topMost - i * LineHeight);
|
|
}
|
|
else if (HAlign == LabelAlignmentH::LEFT)
|
|
{
|
|
newCenter.set(leftMost + s.getWidth() * 0.5, topMost - i * LineHeight);
|
|
}
|
|
else // right
|
|
{
|
|
newCenter.set(rightMost - s.getWidth() * 0.5f, topMost - i * LineHeight);
|
|
}
|
|
delta = newCenter - s.center();
|
|
|
|
s.translate(delta.x, delta.y);
|
|
|
|
textArea.merge(s.asRect());
|
|
|
|
}
|
|
|
|
//no resize
|
|
if (OverFlow == LabelOverflow::NONE || OverFlow == LabelOverflow::CLAMP || OverFlow == LabelOverflow::RESIZE_HEIGHT)
|
|
{
|
|
Vec2 centerArea(textArea.origin.x + textArea.size.width / 2.0, textArea.origin.y + textArea.size.height / 2.0);
|
|
float tPosX, tPosY;
|
|
|
|
float halfAreaWidth = textArea.size.width * 0.5f;
|
|
float halfAreaHeight = textArea.size.height * 0.5f;
|
|
float contentWidth = ContentWidth;
|
|
float contentHeight = ContentHeight;
|
|
if (OverFlow == LabelOverflow::NONE)
|
|
{
|
|
contentWidth = textArea.size.width;
|
|
contentHeight = textArea.size.height;
|
|
}
|
|
|
|
switch (HAlign)
|
|
{
|
|
case LabelAlignmentH::LEFT:
|
|
tPosX = halfAreaWidth - AnchorX * contentWidth;
|
|
break;
|
|
case LabelAlignmentH::CENTER:
|
|
tPosX = (0.5f - AnchorX) * contentWidth;
|
|
break;
|
|
case LabelAlignmentH::RIGHT:
|
|
tPosX = (1.0f - AnchorX) * contentWidth - halfAreaWidth;
|
|
break;
|
|
default:
|
|
assert(false);
|
|
}
|
|
|
|
switch (VAlign)
|
|
{
|
|
case LabelAlignmentV::TOP:
|
|
tPosY = (1.0f - AnchorY) * contentHeight - halfAreaHeight;
|
|
break;
|
|
case LabelAlignmentV::CENTER:
|
|
tPosY = (0.5f - AnchorY) * ContentHeight;
|
|
break;
|
|
case LabelAlignmentV::BOTTOM:
|
|
tPosY = halfAreaHeight - AnchorY * contentHeight;
|
|
break;
|
|
default:
|
|
assert(false);
|
|
}
|
|
|
|
delta = Vec2(tPosX, tPosY) - centerArea;
|
|
|
|
for (auto &textSpace : textSpaces._data) {
|
|
textSpace.translate(delta.x, delta.y);
|
|
}
|
|
|
|
if (OverFlow == LabelOverflow::CLAMP)
|
|
{
|
|
// clipping
|
|
Rect displayRegion(-ContentWidth * AnchorX, -ContentHeight * AnchorY, ContentWidth, ContentHeight);
|
|
for (auto &textSpace : textSpaces._data) {
|
|
textSpace.clip(displayRegion);
|
|
}
|
|
|
|
}
|
|
}
|
|
else if (OverFlow == LabelOverflow::SHRINK)
|
|
{
|
|
float scale = std::min(ContentWidth / textArea.size.width, ContentHeight / textArea.size.height);
|
|
scale = std::min(1.0f, scale);
|
|
_scale = scale;
|
|
Vec2 centerArea(textArea.origin.x + textArea.size.width * 0.5f, textArea.origin.y + textArea.size.height * 0.5f);
|
|
float tPosXScale, tPosYScale;
|
|
|
|
float halfTextWidth = textArea.size.width * 0.5f * _scale;
|
|
float halfTextHeight = textArea.size.height * 0.5f * _scale;
|
|
|
|
switch (HAlign)
|
|
{
|
|
case LabelAlignmentH::LEFT:
|
|
tPosXScale = halfTextWidth - AnchorX * ContentWidth;
|
|
break;
|
|
case LabelAlignmentH::CENTER:
|
|
tPosXScale = (0.5f - AnchorX) * ContentWidth;
|
|
break;
|
|
case LabelAlignmentH::RIGHT:
|
|
tPosXScale = (1.0f - AnchorX) * ContentWidth - halfTextWidth;
|
|
break;
|
|
default:
|
|
assert(false);
|
|
}
|
|
|
|
switch (VAlign)
|
|
{
|
|
case LabelAlignmentV::TOP:
|
|
tPosYScale = (1.0f - AnchorY) * ContentHeight - halfTextHeight;
|
|
break;
|
|
case LabelAlignmentV::CENTER:
|
|
tPosYScale = (0.5f - AnchorY) * ContentHeight;
|
|
break;
|
|
case LabelAlignmentV::BOTTOM:
|
|
tPosYScale = halfTextHeight -AnchorY * ContentHeight;
|
|
break;
|
|
default:
|
|
assert(false);
|
|
}
|
|
delta = Vec2(tPosXScale/ _scale, tPosYScale/_scale) - centerArea;
|
|
for (auto &textSpace : textSpaces._data) {
|
|
textSpace.translate(delta.x, delta.y);
|
|
}
|
|
}
|
|
|
|
|
|
se::Object *comp = _renderer->getJsComponent();
|
|
if ((OverFlow == LabelOverflow::NONE || OverFlow == LabelOverflow::RESIZE_HEIGHT) && comp) {
|
|
se::Value funcVal;
|
|
se::Value nodeVal;
|
|
if(comp->getProperty("node", &nodeVal) && nodeVal.isObject() &&
|
|
nodeVal.toObject()->getProperty("setContentSize", &funcVal)) {
|
|
se::ValueArray args;
|
|
args.push_back(se::Value( OverFlow == LabelOverflow::RESIZE_HEIGHT ? ContentWidth: maxLineWidth));
|
|
args.push_back(se::Value(TotalTextHeight));
|
|
funcVal.toObject()->call(args, nodeVal.toObject());
|
|
}
|
|
}
|
|
|
|
_textSpace = std::move(textSpaces._data);
|
|
|
|
return true;
|
|
}
|
|
|
|
void LabelLayout::fillAssembler(renderer::CustomAssembler *assembler, EffectVariant *templateEffect)
|
|
{
|
|
assembler->reset();
|
|
if(!_groups) {
|
|
return;
|
|
}
|
|
_groups->reset();
|
|
int groupIndex = 0;
|
|
if (_textSpace.empty())
|
|
return;
|
|
|
|
if (_shadowGroups)
|
|
{
|
|
_shadowGroups->reset();
|
|
}
|
|
|
|
assembler->setUseModel(true);
|
|
|
|
auto *fontAtals = _fontAtlas->getFontAtlas();
|
|
|
|
Color4B textColor = _layoutInfo->color;
|
|
|
|
// apply transform to all rectangles
|
|
Rect rect;
|
|
float scale = _scale;
|
|
|
|
const bool Italics = _layoutInfo->italic;
|
|
|
|
if (_shadowGroups && _layoutInfo->shadowBlur >= 0)
|
|
{
|
|
Vec2 offset(_layoutInfo->shadowX, _layoutInfo->shadowY);
|
|
Color4B shadowColor = _layoutInfo->shadowColor;
|
|
for (auto &textSpace : _textSpace) {
|
|
|
|
if (textSpace.isIgnored()) continue;
|
|
|
|
Vec2 shadowOffset = textSpace.getOffset();
|
|
auto size = textSpace.size();
|
|
for (int i = 0; i < size; i++)
|
|
{
|
|
TextRowSpace::GlyphBlock item = textSpace[i];
|
|
if (textSpace.isIgnored(i)) continue;
|
|
|
|
item.area.size = item.area.size * scale;
|
|
item.area.origin = (offset + item.area.origin + shadowOffset) * scale;
|
|
|
|
_shadowGroups->addRect(fontAtals->frameAt(item.texId).getTexture(), item.area, item.uv, shadowColor, Italics);
|
|
}
|
|
}
|
|
|
|
groupIndex = _shadowGroups->fill(assembler, groupIndex, this, templateEffect);
|
|
|
|
Technique::Parameter outlineSizeP(outlineSizeKey, Technique::Parameter::Type::FLOAT, (float*)&(_layoutInfo->outlineSize));
|
|
if (_layoutInfo->outlineSize > 0.0f)
|
|
{
|
|
// use shadow color to replace outline color
|
|
Color4F outlineColor(_layoutInfo->shadowColor);
|
|
Technique::Parameter outlineColorP(outlineColorKey, Technique::Parameter::Type::COLOR4, (float*)&outlineColor);
|
|
for (auto i = 0; i < groupIndex; i++)
|
|
{
|
|
auto *e = assembler->getEffect(i);
|
|
e->setProperty(outlineColorKey, outlineColorP);
|
|
e->setProperty(outlineSizeKey, outlineSizeP);
|
|
}
|
|
}
|
|
else {
|
|
for (auto i = 0; i < groupIndex; i++)
|
|
{
|
|
auto *e = assembler->getEffect(i);
|
|
e->setProperty(outlineSizeKey, outlineSizeP);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
for (auto &textSpace : _textSpace) {
|
|
|
|
if (textSpace.isIgnored()) continue;
|
|
|
|
Vec2 offset = textSpace.getOffset();
|
|
auto size = textSpace.size();
|
|
for (int i = 0; i < size; i++)
|
|
{
|
|
auto &item = textSpace[i];
|
|
if (textSpace.isIgnored(i)) continue;
|
|
|
|
item.area.size = item.area.size * scale;
|
|
item.area.origin = (offset + item.area.origin) * scale;
|
|
|
|
_groups->addRect(fontAtals->frameAt(item.texId).getTexture(), item.area, item.uv, textColor, Italics);
|
|
}
|
|
}
|
|
int textStartIndex = groupIndex;
|
|
groupIndex = _groups->fill(assembler, groupIndex, this, templateEffect);
|
|
|
|
if (_layoutInfo->outlineSize > 0.0f)
|
|
{
|
|
Color4F outlineColor(_layoutInfo->outlineColor);
|
|
Technique::Parameter outlineColorP(outlineColorKey, Technique::Parameter::Type::COLOR4, (float*)&outlineColor);
|
|
Technique::Parameter outlineSizeP(outlineSizeKey, Technique::Parameter::Type::FLOAT, (float*)&(_layoutInfo->outlineSize));
|
|
for (auto i = textStartIndex; i < groupIndex; i++)
|
|
{
|
|
auto *e = assembler->getEffect(i);
|
|
e->setProperty(outlineColorKey, outlineColorP);
|
|
e->setProperty(outlineSizeKey, outlineSizeP);
|
|
}
|
|
}
|
|
else {
|
|
Color4F outlineColor(_layoutInfo->outlineColor);
|
|
Technique::Parameter outlineSizeP(outlineSizeKey, Technique::Parameter::Type::FLOAT, (float*)&(_layoutInfo->outlineSize));
|
|
for (auto i = textStartIndex; i < groupIndex; i++)
|
|
{
|
|
auto *e = assembler->getEffect(i);
|
|
e->setProperty(outlineSizeKey, outlineSizeP);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|