初始化

This commit is contained in:
SmallMain
2022-06-25 00:23:03 +08:00
commit ef0589e8e5
2264 changed files with 617829 additions and 0 deletions

View File

@@ -0,0 +1,361 @@
/****************************************************************************
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 "CCFontAtlas.h"
#include "renderer/gfx/Texture2D.h"
#include "renderer/gfx/DeviceGraphics.h"
#include "base/ccConfig.h"
#include <cassert>
#if CC_ENABLE_TTF_LABEL_RENDERER
static const int PIXEL_PADDING = 2;
namespace cocos2d {
FontAtlasFrame::FontAtlasFrame()
{
_currentRowX = PIXEL_PADDING;
_currentRowY = PIXEL_PADDING;
}
FontAtlasFrame::FontAtlasFrame(FontAtlasFrame&& o)
{
#if CC_ENABLE_CACHE_TTF_FONT_TEXTURE
// move buffer instead of copy
std::swap(_buffer, o._buffer);
_dirtyFlag = o._dirtyFlag;
_dirtyRegion = o._dirtyRegion;
#endif
_WIDTH = o._WIDTH;
_HEIGHT = o._HEIGHT;
_currentRowX = o._currentRowX;
_currentRowY = o._currentRowY;
_currRowHeight = o._currRowHeight;
_pixelMode = o._pixelMode;
_texture = o._texture;
o._texture = nullptr;
}
FontAtlasFrame::~FontAtlasFrame()
{
CC_SAFE_RELEASE(_texture);
}
void FontAtlasFrame::reinit(PixelMode pixelMode, int width, int height)
{
_pixelMode = pixelMode;
_WIDTH = width;
_HEIGHT = height;
_currentRowY = PIXEL_PADDING;
_currentRowY = PIXEL_PADDING;
_currRowHeight = 0;
#if CC_ENABLE_CACHE_TTF_FONT_TEXTURE
_buffer.resize(PixelModeSize(pixelMode) * width * height);
std::fill(_buffer.begin(), _buffer.end(), 0);
_dirtyFlag = 0;
#endif
getTexture(); // init texture
}
FontAtlasFrame::FrameResult FontAtlasFrame::append(int width, int height, std::vector<uint8_t> &data, Rect &out)
{
#if CC_ENABLE_CACHE_TTF_FONT_TEXTURE
assert(_buffer.size() > 0);
assert(width <= _WIDTH && height <= _HEIGHT);
#endif
if (!hasSpace(width, height)) {
return FrameResult::E_FULL;
}
#if CC_ENABLE_CACHE_TTF_FONT_TEXTURE
//update texture-data in CPU memory
const int pixelSize = PixelModeSize(_pixelMode);
uint8_t* dst = _buffer.data();
uint8_t* src = data.data();
uint8_t* dstOrigin = pixelSize * (_currentRowY * _WIDTH + _currentRowX) + dst;
const int BytesEachRow = pixelSize * width;
for (int i = 0; i < height; i++)
{
memcpy(dstOrigin + i * _WIDTH * pixelSize, src + i * BytesEachRow, BytesEachRow);
}
if (_dirtyFlag == 0)
{
_dirtyFlag |= DIRTY_RECT;
_dirtyRegion = Rect(_currentRowX, _currentRowY, width, height);
}
else
{
_dirtyRegion.merge(Rect(_currentRowX, _currentRowY, width, height));
}
#else
//update GPU texture immediately
renderer::Texture::SubImageOption opt;
opt.imageData = data.data();
opt.x = _currentRowX;
opt.y = _currentRowY;
opt.width = width;
opt.height = height;
opt.imageDataLength = data.size();
_texture->updateSubImage(opt);
#endif
out.origin.set(_currentRowX, _currentRowY);
out.size.width = width;
out.size.height = height;
// move cursor
moveToNextCursor(width, height);
return FrameResult::SUCCESS;
}
bool FontAtlasFrame::hasSpace(int width, int height)
{
if (hasRowXSpace(width) && hasYSpace(height)) {
return true;
}
if (hasNextRowXSpace(width) && hasNextRowYSpace(height))
{
moveToNextRow();
return true;
}
return false;
}
int FontAtlasFrame::remainRowXSpace() const
{
return _WIDTH - _currentRowX;
}
int FontAtlasFrame::remainYSpace() const
{
return _HEIGHT - _currentRowY;
}
bool FontAtlasFrame::hasRowXSpace(int x) const
{
return x + PIXEL_PADDING <= remainRowXSpace();
}
bool FontAtlasFrame::hasYSpace(int y) const
{
return y + PIXEL_PADDING <= remainYSpace();
}
bool FontAtlasFrame::hasNextRowXSpace(int x) const
{
return x + PIXEL_PADDING <= _WIDTH;
}
bool FontAtlasFrame::hasNextRowYSpace(int y) const
{
return y + PIXEL_PADDING <= remainYSpace() - _currRowHeight;
}
void FontAtlasFrame::moveToNextRow()
{
_currentRowY += _currRowHeight + PIXEL_PADDING;
_currentRowX = PIXEL_PADDING;
_currRowHeight = 0;
}
void FontAtlasFrame::moveToNextCursor(int width, int height)
{
_currRowHeight = std::max(_currRowHeight, height);
_currentRowX += width + PIXEL_PADDING;
}
renderer::Texture2D * FontAtlasFrame::getTexture()
{
if (!_texture)
{
auto* device = renderer::DeviceGraphics::getInstance();
_texture = new cocos2d::renderer::Texture2D();
cocos2d::renderer::Texture::Options option;
option.width = _WIDTH;
option.height = _HEIGHT;
// alpha only
option.glFormat = GL_ALPHA;
option.glInternalFormat = GL_ALPHA;
option.glType = GL_UNSIGNED_BYTE;
option.bpp = 8 * PixelModeSize(_pixelMode);
renderer::Texture::Image img;
#if CC_ENABLE_CACHE_TTF_FONT_TEXTURE
img.data = _buffer.data();
img.length = _buffer.size();
#else
std::vector<uint8_t> buffer(_WIDTH * _HEIGHT * PixelModeSize(_pixelMode), 0);
img.length = buffer.size();
img.data = buffer.data();
#endif
option.images.push_back(img);
_texture->init(device, option);
}
#if CC_ENABLE_CACHE_TTF_FONT_TEXTURE
if (_dirtyFlag & DIRTY_ALL)
{
renderer::Texture::SubImageOption opt;
opt.imageData = _buffer.data();
opt.x = 0;
opt.y = 0;
opt.width = _WIDTH;
opt.height = _HEIGHT;
opt.imageDataLength = (uint32_t)_buffer.size();
_texture->updateSubImage(opt);
}
else if (_dirtyFlag & DIRTY_RECT)
{
int yMin = _dirtyRegion.getMinY();
int yHeight = _dirtyRegion.size.height;
renderer::Texture::SubImageOption opt;
opt.imageData = _buffer.data() + PixelModeSize(_pixelMode) * _WIDTH * yMin;
opt.x = 0;
opt.y = yMin;
opt.width = _WIDTH;
opt.height = yHeight;
opt.imageDataLength = PixelModeSize(_pixelMode) * _WIDTH * yHeight;
_texture->updateSubImage(opt);
}
_dirtyFlag = 0;
#endif
return _texture;
}
FontAtlas::FontAtlas(PixelMode pixelMode, int width, int height, bool hasoutline)
:_pixelMode(pixelMode), _width(width), _height(height), _useSDF(hasoutline)
{
}
FontAtlas::~FontAtlas()
{
}
bool FontAtlas::init()
{
_textureFrame.reinit(_pixelMode, _width, _height);
_letterMap.clear();
return true;
}
bool FontAtlas::prepareLetter(unsigned long ch, std::shared_ptr<GlyphBitmap> bitmap)
{
if (_letterMap.find(ch) != _letterMap.end())
{
return true;
}
Rect rect;
FontAtlasFrame::FrameResult ret = _textureFrame.append(bitmap->getWidth(), bitmap->getHeight(), bitmap->getData(), rect);
switch (ret) {
case FontAtlasFrame::FrameResult::E_ERROR:
//TODO: ERROR LOG
assert(false);
return false;
case FontAtlasFrame::FrameResult::E_FULL:
// Allocate a new frame & add bitmap the frame
_buffers.push_back(std::move(_textureFrame));
_textureBufferIndex += 1;
_textureFrame.reinit(_pixelMode, _width, _height);
return prepareLetter(ch, bitmap);
case FontAtlasFrame::FrameResult::SUCCESS:
addLetterDef(ch, bitmap, rect);
return true;
default:
//TODO: LOG
assert(false);
}
return false;
}
void FontAtlas::addLetterDef(unsigned long ch, std::shared_ptr<GlyphBitmap> bitmap, const Rect& rect)
{
assert(bitmap->getPixelMode() == _pixelMode);
auto& def = _letterMap[ch];
def.validate = true;
def.textureID = _textureBufferIndex;
def.xAdvance = bitmap->getXAdvance();
def.rect = bitmap->getRect();
def.texX = (rect.origin.x - 0.5f) / _textureFrame.getWidth();
def.texY = (rect.origin.y -0.5f)/ _textureFrame.getHeight();
def.texWidth = (rect.size.width + 1.0f) / _textureFrame.getWidth();
def.texHeight = (rect.size.height + 1.0f) / _textureFrame.getHeight();
def.outline = bitmap->getOutline();
}
bool FontAtlas::prepareLetters(const std::u32string &text, cocos2d::FontFreeType *font)
{
bool ok = true;
for (int i = 0; i < text.length(); i++)
{
auto it = _letterMap.find(text[i]);
if(it == _letterMap.end())
{
auto glyph = font->getGlyphBitmap(text[i], _useSDF);
ok &= prepareLetter(text[i], glyph);
}
}
return ok;
}
FontLetterDefinition* FontAtlas::getOrLoad(unsigned long ch, cocos2d::FontFreeType* font)
{
auto it = _letterMap.find(ch);
if (it != _letterMap.end()) return &it->second;
if (font) {
auto bitmap = font->getGlyphBitmap(ch, _useSDF);
if (bitmap) {
if (prepareLetter(ch, bitmap)) {
return getOrLoad(ch, nullptr);
}
}
}
return nullptr;
}
FontAtlasFrame& FontAtlas::frameAt(int idx)
{
return idx == _textureBufferIndex ? _textureFrame : _buffers.at(idx);
}
}
#endif

View File

@@ -0,0 +1,147 @@
/****************************************************************************
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.
****************************************************************************/
#pragma once
#include "CCFontFreetype.h"
#include <unordered_map>
#include <algorithm>
#include "base/ccConfig.h"
#if CC_ENABLE_TTF_LABEL_RENDERER
namespace cocos2d {
namespace renderer {
class Texture2D;
}
class FontAtlas;
struct FontLetterDefinition
{
float texX = 0, texY = 0;
float texWidth = 0, texHeight = 0;
Rect rect;
int textureID = -1;
float xAdvance = 0;
int outline = 0;
bool validate = false;
};
class FontAtlasFrame
{
public:
enum class FrameResult {
SUCCESS,
E_FULL,
E_ERROR
};
FontAtlasFrame();
FontAtlasFrame(FontAtlasFrame&&); //move
virtual ~FontAtlasFrame();
void reinit(PixelMode mode, int width, int height);
FrameResult append(int width, int height, std::vector<uint8_t> &, Rect &out);
float getWidth() const { return _WIDTH; }
float getHeight() const { return _HEIGHT; }
renderer::Texture2D* getTexture();
private:
enum DirtyType {
DIRTY_RECT = 1,
DIRTY_ALL= 2,
};
bool hasSpace(int width, int height);
int remainRowXSpace() const;
int remainYSpace() const;
bool hasRowXSpace(int x) const;
bool hasYSpace(int y) const;
bool hasNextRowXSpace(int x) const;
bool hasNextRowYSpace(int y) const;
void moveToNextRow();
void moveToNextCursor(int width, int height);
#if CC_ENABLE_CACHE_TTF_FONT_TEXTURE
mutable std::vector<uint8_t> _buffer;
int _dirtyFlag = 0;
Rect _dirtyRegion;
#endif
int _WIDTH = 0;
int _HEIGHT = 0;
int _currentRowY = 0;
int _currentRowX = 0;
int _currRowHeight = 0;
PixelMode _pixelMode = PixelMode::A8;
renderer::Texture2D *_texture = nullptr;
friend class FontAtlas;
};
class FontAtlas {
public:
FontAtlas(PixelMode mode, int width, int height, bool hasoutline);
virtual ~FontAtlas();
bool init();
bool prepareLetter(unsigned long ch, std::shared_ptr<GlyphBitmap> bitmap);
bool prepareLetters(const std::u32string &text, cocos2d::FontFreeType *font);
FontLetterDefinition* getOrLoad(unsigned long ch, FontFreeType* font);
FontAtlasFrame& frameAt(int idx);
private:
void addLetterDef(unsigned long ch, std::shared_ptr<GlyphBitmap> bitmap, const Rect& rect);
std::unordered_map<uint64_t, FontLetterDefinition> _letterMap;
FontAtlasFrame _textureFrame;
std::vector<FontAtlasFrame> _buffers;
int _textureBufferIndex = 0;
int _width = 0;
int _height = 0;
PixelMode _pixelMode = PixelMode::A8;
bool _useSDF = false;
};
}
#endif

View File

@@ -0,0 +1,415 @@
/****************************************************************************
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 "CCFontFreetype.h"
#include <cassert>
#include "platform/CCFileUtils.h"
#include "platform/CCDevice.h"
#include "external/sources/edtaa3func/edtaa3func.h"
#include "base/ccConfig.h"
#if CC_ENABLE_TTF_LABEL_RENDERER
/**
* By enable FFT_USE_SCREEN_DPI, text will be much more clear in mobile devices,
* and more memory will be consumed.
*/
#define FFT_USE_SCREEN_DPI 0
#if FFT_USE_SCREEN_DPI
#define SCALE_BY_DPI(x) (int)((x) * 72 / _dpi)
#else
#define SCALE_BY_DPI(x) (int)(x)
#endif
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
// `thread_local` can not compile on iOS 9.0 below device
# define FFT_SDF_TMP_VECTOR (__IPHONE_OS_VERSION_MIN_REQUIRED >= 90000)
#else
# define FFT_SDF_TMP_VECTOR 1
#endif // CC_TARGET_PLATFORM == CC_PLATFORM_IOS
#if FFT_SDF_TMP_VECTOR
namespace {
//cache vector in thread
thread_local std::vector<short> xdistV;
thread_local std::vector<short> ydistV;
thread_local std::vector<double> gxV;
thread_local std::vector<double> gyV;
thread_local std::vector<double> dataV;
thread_local std::vector<double> outsideV;
thread_local std::vector<double> insideV;
}
#endif
namespace cocos2d {
class FontFreeTypeLibrary {
public:
FontFreeTypeLibrary() {
memset(&_library, 0, sizeof(FT_Library));
FT_Init_FreeType(&_library);
}
~FontFreeTypeLibrary()
{
_faceCache.clear();
FT_Done_FreeType(_library);
}
FT_Library * get() { return &_library; }
std::unordered_map<std::string, std::shared_ptr<Data>> &getFaceCache() {
return _faceCache;
}
private:
std::unordered_map<std::string, std::shared_ptr<Data>> _faceCache;
FT_Library _library;
};
namespace {
std::shared_ptr<FontFreeTypeLibrary> _sFTLibrary;
PixelMode FTtoPixelModel(FT_Pixel_Mode mode)
{
switch (mode)
{
case FT_PIXEL_MODE_GRAY:
return PixelMode::A8;
case FT_PIXEL_MODE_LCD:
return PixelMode::RGB888;
case FT_PIXEL_MODE_BGRA:
return PixelMode::BGRA8888;
default:
assert(false); //invalidate pixelmode
return PixelMode::INVAL;
}
}
std::vector<uint8_t> makeDistanceMap(unsigned char *img, long width, long height, int distanceMapSpread)
{
long pixelAmount = (width + 2 * distanceMapSpread) * (height + 2 * distanceMapSpread);
#if FFT_SDF_TMP_VECTOR
xdistV.resize(pixelAmount);
ydistV.resize(pixelAmount);
gxV.resize(pixelAmount);
gyV.resize(pixelAmount);
dataV.resize(pixelAmount);
outsideV.resize(pixelAmount);
insideV.resize(pixelAmount);
std::fill(gxV.begin(), gxV.end(), 0.0);
std::fill(gyV.begin(), gyV.end(), 0.0);
std::fill(dataV.begin(), dataV.end(), 0.0);
std::fill(outsideV.begin(), outsideV.end(), 0.0);
std::fill(insideV.begin(), insideV.end(), 0.0);
short * xdist = xdistV.data();
short * ydist = ydistV.data();
double * gx = gxV.data();
double * gy = gyV.data();
double * data = dataV.data();
double * outside = outsideV.data();
double * inside = insideV.data();
#else
short * xdist = (short *)malloc(pixelAmount * sizeof(short));
short * ydist = (short *)malloc(pixelAmount * sizeof(short));
double * gx = (double *)calloc(pixelAmount, sizeof(double));
double * gy = (double *)calloc(pixelAmount, sizeof(double));
double * data = (double *)calloc(pixelAmount, sizeof(double));
double * outside = (double *)calloc(pixelAmount, sizeof(double));
double * inside = (double *)calloc(pixelAmount, sizeof(double));
#endif
long i, j;
// Convert img into double (data) rescale image levels between 0 and 1
long outWidth = width + 2 * distanceMapSpread;
for (i = 0; i < width; ++i)
{
for (j = 0; j < height; ++j)
{
data[(j + distanceMapSpread) * outWidth + distanceMapSpread + i] = img[j * width + i] / 255.0;
}
}
width += 2 * distanceMapSpread;
height += 2 * distanceMapSpread;
// Transform background (outside contour, in areas of 0's)
computegradient(data, (int)width, (int)height, gx, gy);
edtaa3(data, gx, gy, (int)width, (int)height, xdist, ydist, outside);
for (i = 0; i < pixelAmount; i++)
if (outside[i] < 0.0)
outside[i] = 0.0;
// Transform foreground (inside contour, in areas of 1's)
for (i = 0; i < pixelAmount; i++)
data[i] = 1 - data[i];
computegradient(data, (int)width, (int)height, gx, gy);
edtaa3(data, gx, gy, (int)width, (int)height, xdist, ydist, inside);
for (i = 0; i < pixelAmount; i++)
if (inside[i] < 0.0)
inside[i] = 0.0;
// The bipolar distance field is now outside-inside
double dist;
/* Single channel 8-bit output (bad precision and range, but simple) */
std::vector<uint8_t> out;
out.resize(pixelAmount);
for (i = 0; i < pixelAmount; i++)
{
dist = outside[i] - inside[i];
dist = 128.0 - dist * 16;
if (dist < 0) dist = 0;
if (dist > 255) dist = 255;
out[i] = (unsigned char)dist;
}
/* Dual channel 16-bit output (more complicated, but good precision and range) */
/*unsigned char *out = (unsigned char *) malloc( pixelAmount * 3 * sizeof(unsigned char) );
for( i=0; i< pixelAmount; i++)
{
dist = outside[i] - inside[i];
dist = 128.0 - dist*16;
if( dist < 0.0 ) dist = 0.0;
if( dist >= 256.0 ) dist = 255.999;
// R channel is a copy of the original grayscale image
out[3*i] = img[i];
// G channel is fraction
out[3*i + 1] = (unsigned char) ( 256 - (dist - floor(dist)* 256.0 ));
// B channel is truncated integer part
out[3*i + 2] = (unsigned char)dist;
}*/
#if !FFT_SDF_TMP_VECTOR
free(xdist);
free(ydist);
free(gx);
free(gy);
free(data);
free(outside);
free(inside);
#endif
return out;
}
}
FontFreeType::FontFreeType(const std::string& fontName, float fontSize, LabelLayoutInfo *info)
{
if (!_sFTLibrary)
{
_sFTLibrary = std::make_shared< FontFreeTypeLibrary>();
}
_fontName = fontName;
_fontSize = fontSize;
_info = info;
#if FFT_USE_SCREEN_DPI
_dpi = Device::getDPI();
#else
_dpi = 72;
#endif
}
FontFreeType::~FontFreeType()
{
//CCLOG("~FontFreeType");
if (_stroker) FT_Stroker_Done(_stroker);
if (_face) FT_Done_Face(_face);
}
FT_Library& FontFreeType::getFTLibrary()
{
return *(_sFTLibrary->get());
}
bool FontFreeType::loadFont()
{
std::shared_ptr<Data> faceData;
auto itr = _sFTLibrary->getFaceCache().find(_fontName);
if (itr == _sFTLibrary->getFaceCache().end()) {
faceData = std::make_shared<Data>(FileUtils::getInstance()->getDataFromFile(_fontName));
_sFTLibrary->getFaceCache()[_fontName] = faceData;
} else {
faceData = itr->second;
}
if (FT_New_Memory_Face(getFTLibrary(), faceData->getBytes(), faceData->getSize(), 0, &_face))
{
cocos2d::log("[error] failed to parse font %s", _fontName.c_str());
return false;
}
_fontFaceData = faceData;
if (FT_Select_Charmap(_face, _encoding))
{
int foundIndex = -1;
for (int charmapIndex = 0; charmapIndex < _face->num_charmaps; charmapIndex++)
{
if (_face->charmaps[charmapIndex]->encoding != FT_ENCODING_NONE)
{
foundIndex = charmapIndex;
break;
}
}
if (foundIndex == -1)
{
return false;
}
_encoding = _face->charmaps[foundIndex]->encoding;
if (FT_Select_Charmap(_face, _encoding))
{
return false;
}
}
int fontSizeInPoints = (int)(64.0f * _fontSize);
if (FT_Set_Char_Size(_face, fontSizeInPoints, fontSizeInPoints, _dpi, _dpi))
{
return false;
}
_lineHeight = SCALE_BY_DPI((_face->size->metrics.ascender - _face->size->metrics.descender) >> 6);
return true;
}
int FontFreeType::getHorizontalKerningForChars(uint64_t a, uint64_t b) const
{
auto idx1 = FT_Get_Char_Index(_face, static_cast<FT_ULong>(a));
if (!idx1)
return 0;
auto idx2 = FT_Get_Char_Index(_face, static_cast<FT_ULong>(b));
if (!idx2)
return 0;
FT_Vector kerning;
if (FT_Get_Kerning(_face, idx1, idx2, FT_KERNING_DEFAULT, &kerning))
return 0;
return SCALE_BY_DPI(kerning.x >> 6);
}
std::unique_ptr<std::vector<int>> FontFreeType::getHorizontalKerningForUTF32Text(const std::u32string& text) const
{
if (!_face) return nullptr;
if (FT_HAS_KERNING(_face) == 0) return nullptr;
const auto letterNum = text.length();
std::vector<int>* sizes = new std::vector<int>(letterNum, 0);
for (int i = 1; i < letterNum; i++)
{
(*sizes)[i] = SCALE_BY_DPI(getHorizontalKerningForChars(text[i - 1], text[i]));
}
return std::unique_ptr<std::vector<int>>(sizes);
}
int FontFreeType::getFontAscender() const
{
return SCALE_BY_DPI(_face->size->metrics.ascender >> 6);
}
const char* FontFreeType::getFontFamily() const
{
if (!_face) return nullptr;
return _face->family_name;
}
std::shared_ptr<GlyphBitmap> FontFreeType::getGlyphBitmap(unsigned long ch, bool hasOutline)
{
return hasOutline ? getSDFGlyphBitmap(ch) : getNormalGlyphBitmap(ch);
}
std::shared_ptr<GlyphBitmap> FontFreeType::getNormalGlyphBitmap(unsigned long ch)
{
if (!_face) return nullptr;
const auto load_char_flag = FT_LOAD_RENDER | FT_LOAD_NO_AUTOHINT;
if (FT_Load_Char(_face, static_cast<FT_ULong>(ch), load_char_flag))
{
return nullptr;
}
auto& metrics = _face->glyph->metrics;
int x = SCALE_BY_DPI(metrics.horiBearingX >> 6);
int y = SCALE_BY_DPI(-(metrics.horiBearingY >> 6));
int w = SCALE_BY_DPI(metrics.width >> 6);
int h = SCALE_BY_DPI(metrics.height >> 6);
int adv = SCALE_BY_DPI(metrics.horiAdvance >> 6);
auto& bitmap = _face->glyph->bitmap;
int bmWidth = bitmap.width;
int bmHeight = bitmap.rows;
PixelMode mode = FTtoPixelModel(static_cast<FT_Pixel_Mode>(bitmap.pixel_mode));
int size = PixelModeSize(mode) * bmWidth * bmHeight;
std::vector<uint8_t> data((uint8_t*)bitmap.buffer, (uint8_t*)bitmap.buffer + size);
auto* ret = new GlyphBitmap(data, bmWidth, bmHeight, Rect(x, y, w, h), adv, mode, 0);
return std::shared_ptr<GlyphBitmap>(ret);
}
std::shared_ptr<GlyphBitmap> FontFreeType::getSDFGlyphBitmap(unsigned long ch)
{
if (!_face) return nullptr;
const auto load_char_flag = FT_LOAD_RENDER | FT_LOAD_NO_AUTOHINT;
if (FT_Load_Char(_face, static_cast<FT_ULong>(ch), load_char_flag))
{
return nullptr;
}
auto& metrics = _face->glyph->metrics;
int x = SCALE_BY_DPI(metrics.horiBearingX >> 6);
int y = SCALE_BY_DPI(-(metrics.horiBearingY >> 6));
int w = SCALE_BY_DPI(metrics.width >> 6);
int h = SCALE_BY_DPI(metrics.height >> 6);
int adv = SCALE_BY_DPI(metrics.horiAdvance >> 6);
auto& bitmap = _face->glyph->bitmap;
int bmWidth = bitmap.width;
int bmHeight = bitmap.rows;
PixelMode mode = FTtoPixelModel(static_cast<FT_Pixel_Mode>(bitmap.pixel_mode));
assert(mode == PixelMode::A8);
int dms = std::max(3, (int)std::max(0.2 * bmWidth, 0.2 * bmHeight));
// int size = PixelModeSize(mode) * bmWidth * bmHeight;
std::vector<uint8_t> data = makeDistanceMap(bitmap.buffer, bmWidth, bmHeight, dms);
auto* ret = new GlyphBitmap(data, bmWidth + 2 * dms, bmHeight + 2 * dms, Rect(x, y, w + 2 * dms, h + 2 * dms), adv, mode, dms);
return std::shared_ptr<GlyphBitmap>(ret);
}
}
#endif

View File

@@ -0,0 +1,93 @@
/****************************************************************************
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.
****************************************************************************/
#pragma once
//#include "freetype2/ft2build.h"
#include "freetype/ft2build.h"
#include FT_FREETYPE_H
#include FT_STROKER_H
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include "CCTTFTypes.h"
#include "base/CCData.h"
#include "base/ccConfig.h"
#if CC_ENABLE_TTF_LABEL_RENDERER
namespace cocos2d {
class FontFreeTypeLibrary;
struct LabelLayoutInfo;
class FontFreeType
{
public:
//const static int DistanceMapSpread;
FontFreeType(const std::string& fontName, float fontSize, LabelLayoutInfo* info);
virtual ~FontFreeType();
FT_Library& getFTLibrary();
bool loadFont();
int getHorizontalKerningForChars(uint64_t a, uint64_t b) const;
std::unique_ptr<std::vector<int>> getHorizontalKerningForUTF32Text(const std::u32string &text) const;
int getFontAscender() const;
const char* getFontFamily() const;
std::shared_ptr<GlyphBitmap> getGlyphBitmap(unsigned long ch, bool hasOutline = false);
private:
std::shared_ptr<GlyphBitmap> getNormalGlyphBitmap(unsigned long ch);
std::shared_ptr<GlyphBitmap> getSDFGlyphBitmap(unsigned long ch);
//weak reference
LabelLayoutInfo *_info = nullptr;
float _fontSize = 0.0f;
float _lineHeight = 0.0f;
std::string _fontName;
std::shared_ptr<Data> _fontFaceData;
FT_Face _face = { 0 };
FT_Stroker _stroker = { 0 };
FT_Encoding _encoding = FT_ENCODING_UNICODE;
int _dpi = 72;
};
}
#endif

View File

@@ -0,0 +1,920 @@
/****************************************************************************
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

View File

@@ -0,0 +1,168 @@
/****************************************************************************
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.
****************************************************************************/
#pragma once
#include <string>
#include "2d/CCTTFLabelAtlasCache.h"
#include "math/Vec3.h"
#include "math/Vec4.h"
#include "base/ccConfig.h"
#if CC_ENABLE_TTF_LABEL_RENDERER
namespace cocos2d {
namespace renderer {
class CustomAssembler;
class Texture2D;
class MeshBuffer;
class Effect;
class EffectVariant;
}
namespace middleware {
class MeshBuffer;
}
class TextRenderGroup;
class LabelRenderer;
class TextRowSpace {
/**
* Y
* ^
* |
* |
* +------> X
*/
public:
struct GlyphBlock {
Rect area;
Rect uv;
int texId = 0;
bool ignored = false;
};
TextRowSpace() = default;
TextRowSpace(TextRowSpace &&other);
void fillRect(int texId, Rect &rect, Rect &uv);
void translate(float x, float y);
Vec2 center() const;
float getWidth() const { return _data.size() > 0 ? _right - _left : 0; }
float getHeight() const { return _data.size() > 0 ? _top - _bottom : 0; }
float getExtentedWidth(float left, float right) const;
Vec2 getOffset() const { return Vec2(_x, _y); }
GlyphBlock & operator[] (size_t i);
bool isIgnored() const { return _ignored; }
bool isIgnored(int i) const { return _data[i].ignored; }
void clip(const Rect &rec);
size_t size() const { return _data.size(); }
bool validate() const { return _data.size() > 0; }
Rect asRect() const;
float getLeft() const { return _left; }
float getRight() const { return _right; }
float getTop() const { return _top; }
float getBottom() const { return _bottom; }
private:
void reset();
GlyphBlock & appendBlock();
private:
float _left = FLT_MAX;
float _bottom = FLT_MAX;
float _right = FLT_MIN;
float _top = FLT_MIN;
float _x = 0.0f;
float _y = 0.0f;
std::vector<GlyphBlock> _data;
bool _ignored = false;
};
class LabelLayout {
public:
LabelLayout(LabelRenderer *r): _renderer(r){}
bool init(const std::string& font, const std::string& text, float fontSize, float retinaFontSize, LabelLayoutInfo *info);
virtual ~LabelLayout();
void setString(const std::string &txt, bool forceUpdate);
bool isInited() const { return _inited; }
void fillAssembler(renderer::CustomAssembler *assembeler, renderer::EffectVariant *effect);
private:
bool updateContent();
private:
std::string _string;
std::u32string _u32string;
std::string _font;
float _fontSize = 0.0f;
float _retinaFontSize = 0.0f;
float _fontScale = 1.0f;
float _scale = 1.0f;
//weak reference
LabelLayoutInfo *_layoutInfo = nullptr;
std::shared_ptr<TTFLabelAtlas> _fontAtlas;
bool _enableKerning = true;
bool _inited = false;
std::vector<TextRowSpace> _textSpace;
std::shared_ptr<TextRenderGroup> _groups;
std::shared_ptr<TextRenderGroup> _shadowGroups;
LabelRenderer *_renderer = nullptr;
};
}
#endif

View File

@@ -0,0 +1,155 @@
/****************************************************************************
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 "CCTTFLabelAtlasCache.h"
#include "platform/CCFileUtils.h"
#include <cmath>
#include <cassert>
#include "base/ccConfig.h"
#if CC_ENABLE_TTF_LABEL_RENDERER
#define FTT_MAP_FONT_SIZE 0
#define FTT_USE_FONT_MAP_SIZE_POWEROFTWO 0
#define FTT_TEXTURE_SIZE 1024
namespace cocos2d {
namespace {
TTFLabelAtlasCache *_instance = nullptr;
/**
* Labels of different size can share a same FontAtlas by mapping font size.
*/
#if FTT_MAP_FONT_SIZE
inline int mapFontSize(float x) {
#if FTT_USE_FONT_MAP_SIZE_POWEROFTWO
// round to power of 2, which waste lots of memory?
return 1 << (int)(::ceil(::log(x) / ::log(2.0)));
#else
if (x <= 8.0f) return 8;
else if (x <= 16.0f) return 16;
else if (x <= 32.0f) return 32;
else if (x <= 48.0f) return 48;
return (int)x;
#endif
}
#else
#define mapFontSize(f) (int)(f)
#endif
}
TTFLabelAtlas::TTFLabelAtlas(const std::string &fontPath, float fontSize, LabelLayoutInfo *info)
:_fontName(fontPath), _fontSize(fontSize), _info(info)
{
}
bool TTFLabelAtlas::init()
{
assert(FileUtils::getInstance()->isFileExist(_fontName));
_ttfFont = std::make_shared<FontFreeType>(_fontName, _fontSize, _info);
if(!_ttfFont->loadFont()){
return false;
}
_fontAtlas = std::make_shared<FontAtlas>(PixelMode::A8, FTT_TEXTURE_SIZE, FTT_TEXTURE_SIZE, _info->outlineSize > 0 || _info->bold);
_fontAtlas->init();
return true;
}
TTFLabelAtlasCache * TTFLabelAtlasCache::getInstance()
{
if (!_instance)
{
_instance = new TTFLabelAtlasCache();
}
return _instance;
}
void TTFLabelAtlasCache::destroyInstance()
{
delete _instance;
_instance = nullptr;
}
void TTFLabelAtlasCache::reset()
{
_cache.clear();
}
std::shared_ptr<TTFLabelAtlas> TTFLabelAtlasCache::load(const std::string &font, float fontSizeF, LabelLayoutInfo *info)
{
int fontSize = mapFontSize(fontSizeF);
std::string keybuffer = cacheKeyFor(font, fontSize, info);
#if CC_TTF_LABELATLAS_ENABLE_GC
std::weak_ptr<TTFLabelAtlas> &atlasWeak= _cache[keybuffer];
std::shared_ptr<TTFLabelAtlas> atlas = atlasWeak.lock();
if (!atlas)
{
atlas = std::make_shared<TTFLabelAtlas>(font, fontSize, info);
if(!atlas->init())
{
return nullptr;
}
atlasWeak = atlas;
}
#else
std::shared_ptr<TTFLabelAtals> &atlas = _cache[keybuffer];
if (!atlas)
{
atlas = std::make_shared<TTFLabelAtals>(font, fontSize, info);
if(!atlas->init())
{
return nullptr;
}
}
#endif
return atlas;
}
void TTFLabelAtlasCache::unload(TTFLabelAtlas *atlas)
{
int fontSize = mapFontSize(atlas->_fontSize);
std::string key = cacheKeyFor(atlas->_fontName, fontSize, atlas->_info);
_cache.erase(key);
}
std::string TTFLabelAtlasCache::cacheKeyFor(const std::string &font, int fontSize, LabelLayoutInfo * info)
{
char keybuffer[512] = { 0 };
std::string fullPath = FileUtils::getInstance()->fullPathForFilename(font);
//NOTICE: fontpath may be too long
snprintf(keybuffer, 511, "s:%d/sdf:%d/p:%s", fontSize, info->outlineSize > 0 || info->bold, fullPath.c_str());
return keybuffer;
}
}
#endif

View File

@@ -0,0 +1,96 @@
/****************************************************************************
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.
****************************************************************************/
#pragma once
#include "2d/CCFontFreetype.h"
#include "2d/CCFontAtlas.h"
#include <unordered_map>
#include <memory>
#include "base/ccConfig.h"
#if CC_ENABLE_TTF_LABEL_RENDERER
#define CC_TTF_LABELATLAS_ENABLE_GC 1
namespace cocos2d {
class TTFLabelAtlasCache;
struct LabelLayoutInfo;
// font atlas of specific size font
class TTFLabelAtlas {
public:
TTFLabelAtlas(const std::string &, float, LabelLayoutInfo *info);
inline FontAtlas * getFontAtlas() const { return _fontAtlas.get(); }
inline FontFreeType * getTTF() const { return _ttfFont.get(); }
float getFontSize() const { return _fontSize; }
bool init();
private:
std::string _fontName;
float _fontSize = 0.f;
//weak reference
LabelLayoutInfo *_info = nullptr;
std::shared_ptr<FontAtlas> _fontAtlas;
std::shared_ptr<FontFreeType> _ttfFont;
friend class TTFLabelAtlasCache;
};
class TTFLabelAtlasCache {
public:
static TTFLabelAtlasCache* getInstance();
static void destroyInstance();
void reset();
std::shared_ptr<TTFLabelAtlas> load(const std::string &font, float fontSize, LabelLayoutInfo* info);
void unload(TTFLabelAtlas *);
protected:
std::string cacheKeyFor(const std::string &font, int fontSize, LabelLayoutInfo *info);
TTFLabelAtlasCache() {}
private:
#if CC_TTF_LABELATLAS_ENABLE_GC
std::unordered_map< std::string, std::weak_ptr< TTFLabelAtlas>> _cache;
#else
std::unordered_map< std::string, std::shared_ptr< TTFLabelAtals>> _cache;
#endif
};
}
#endif

View File

@@ -0,0 +1,167 @@
/****************************************************************************
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 "cocos/2d/CCTTFLabelRenderer.h"
#include "MiddlewareMacro.h"
#include "renderer/renderer/Pass.h"
#include "renderer/renderer/Technique.h"
#include "renderer/scene/RenderFlow.hpp"
#include "renderer/scene/assembler/CustomAssembler.hpp"
#include "cocos/editor-support/IOBuffer.h"
#include "cocos/editor-support/middleware-adapter.h"
#include "cocos/editor-support/MiddlewareManager.h"
#include "cocos/platform/CCApplication.h"
#include "cocos/platform/CCFileUtils.h"
#include "scripting/js-bindings/jswrapper/SeApi.h"
#include "CCLabelLayout.h"
#include "base/ccConfig.h"
#if CC_ENABLE_TTF_LABEL_RENDERER
USING_NS_CC;
USING_NS_MW;
using namespace renderer;
namespace cocos2d {
LabelRenderer::LabelRenderer()
{
}
LabelRenderer::~LabelRenderer()
{
CC_SAFE_RELEASE_NULL(_effect);
CC_SAFE_RELEASE_NULL(_nodeProxy);
if(_componentObj) _componentObj->decRef();
}
void LabelRenderer::bindNodeProxy(cocos2d::renderer::NodeProxy* node) {
if (node == _nodeProxy) return;
CC_SAFE_RELEASE(_nodeProxy);
_nodeProxy = node;
CC_SAFE_RETAIN(_nodeProxy);
}
void LabelRenderer::setEffect(cocos2d::renderer::EffectVariant* arg) {
if (_effect == arg) return;
CC_SAFE_RELEASE(_effect);
_effect = arg;
CC_SAFE_RETAIN(arg);
_cfg->updateFlags |= UPDATE_EFFECT;
}
void LabelRenderer::bindSharedBlock(se::Object * selfObj, void *cfg, void *layout)
{
_selfObj = selfObj;
_cfg = static_cast<decltype(_cfg)>(cfg);
_layoutInfo = static_cast<decltype(_layoutInfo)>(layout);
}
void LabelRenderer::genStringLayout()
{
std::string fontPath = getFontPath();
std::string text = getString();
if (!fontPath.empty() && !text.empty() && !_stringLayout)
{
_stringLayout.reset(new LabelLayout(this));
_stringLayout->init(fontPath, text, _cfg->fontSize, _cfg->fontSizeRetina, _layoutInfo);
}
}
void LabelRenderer::render()
{
std::string text = getString();
std::string fontPath = getFontPath();
if (!_effect || text.empty() || fontPath.empty()) return;
if (!_stringLayout) {
genStringLayout();
_cfg->updateFlags &= ~(UPDATE_FONT | UPDATE_EFFECT);
}
renderIfChange();
}
void LabelRenderer::renderIfChange()
{
if (!_stringLayout) return;
if (_cfg->updateFlags & UPDATE_FONT || _cfg->updateFlags & UPDATE_EFFECT)
{
// update font & string
_stringLayout.reset();
genStringLayout();
doRender();
}
else if (_cfg->updateFlags & UPDATE_CONTENT)
{
std::string text = getString(); // update content only
if (_stringLayout->isInited())
{
_stringLayout->setString(text, true);
doRender();
}
}
_cfg->updateFlags = 0;
}
void LabelRenderer::doRender()
{
if (_stringLayout && _effect && _nodeProxy && _nodeProxy->getAssembler())
{
auto *assembler = (CustomAssembler*)_nodeProxy->getAssembler();
_stringLayout->fillAssembler(assembler, _effect);
}
}
std::string LabelRenderer::getString() {
se::Value str;
assert(_selfObj);
_selfObj->getProperty("string", &str);
return str.toString();
}
std::string LabelRenderer::getFontPath() {
se::Value str;
assert(_selfObj);
_selfObj->getProperty("fontPath", &str);
return str.toString();
}
void LabelRenderer::setJsComponent(se::Object *component)
{
if(_componentObj == component) return;
if(_componentObj) _componentObj->decRef();
if(component) component->incRef();
_componentObj = component;
}
}
#endif

View File

@@ -0,0 +1,106 @@
/****************************************************************************
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.
****************************************************************************/
#pragma once
#include "base/ccMacros.h"
#include "2d/CCTTFTypes.h"
#include "base/ccConfig.h"
#include <memory>
#if CC_ENABLE_TTF_LABEL_RENDERER
namespace se {
class Object;
}
namespace cocos2d {
namespace renderer {
class Texture2D;
class Effect;
class EffectVariant;
class NodeProxy;
}
class LabelLayout;
class LabelRenderer : public cocos2d::Ref {
private:
enum UpdateFlags {
UPDATE_CONTENT = 1 << 0,
UPDATE_FONT = 1 << 1,
UPDATE_EFFECT = 1 << 2
};
public:
struct LabelRendererConfig {
uint32_t updateFlags = 0xFFFFFFFF;
float fontSize = 20.0f;
float fontSizeRetina = 0.0f;
};
LabelRenderer();
virtual ~LabelRenderer();
void bindNodeProxy(cocos2d::renderer::NodeProxy* node);
void setEffect(cocos2d::renderer::EffectVariant* effect);
void render();
void bindSharedBlock(se::Object *selfObj, void *cfg, void *layout);
void setJsComponent(se::Object *component);
se::Object *getJsComponent() const {return _componentObj;}
private:
void genStringLayout();
void renderIfChange();
void doRender();
std::string getString();
std::string getFontPath();
std::unique_ptr<LabelLayout> _stringLayout;
se::Object *_selfObj = nullptr;
se::Object *_componentObj = nullptr;
//export arraybuffer to js
LabelRendererConfig *_cfg = nullptr;
LabelLayoutInfo *_layoutInfo = nullptr;
cocos2d::renderer::NodeProxy* _nodeProxy = nullptr;
cocos2d::renderer::EffectVariant * _effect = nullptr;
};
}
#endif

View File

@@ -0,0 +1,69 @@
/****************************************************************************
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 "CCTTFTypes.h"
#include <cassert>
#include <cstdarg>
#include <iostream>
#include "base/ccConfig.h"
#if CC_ENABLE_TTF_LABEL_RENDERER
namespace cocos2d {
GlyphBitmap::GlyphBitmap(std::vector<uint8_t>& data, int width, int height, Rect rect, int adv, PixelMode mode, int outline)
: _data(std::move(data)), _width(width), _height(height), _rect(rect), _xAdvance(adv), _pixelMode(mode), _outline(outline)
{
}
GlyphBitmap::GlyphBitmap(GlyphBitmap&& other) noexcept
{
_data = std::move(other._data);
_rect = other._rect;
_width = other._width;
_height = other._height;
_xAdvance = other._xAdvance;
_pixelMode = other._pixelMode;
_outline = other._outline;
}
int PixelModeSize(PixelMode mode)
{
switch (mode)
{
case PixelMode::AI88:
return 2;
case PixelMode::A8:
return 1;
case PixelMode::RGB888:
return 3;
case PixelMode::BGRA8888:
return 4;
default:
assert(false); // invalidate pixel mode
}
return 0;
}
}
#endif

View File

@@ -0,0 +1,114 @@
/****************************************************************************
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.
****************************************************************************/
#pragma once
#include <memory>
#include <vector>
#include <iosfwd>
#include <cstdint>
#include "math/Vec2.h"
#include "math/CCGeometry.h"
#include "base/ccTypes.h"
#include "base/ccConfig.h"
#if CC_ENABLE_TTF_LABEL_RENDERER
namespace cocos2d {
// ref CCMacro.js
enum class LabelAlignmentH : uint8_t
{
LEFT, CENTER, RIGHT
};
// ref CCMacro.js
enum class LabelAlignmentV : uint8_t {
TOP, CENTER, BOTTOM
};
// ref CCLabel.js
enum class LabelOverflow : uint8_t {
NONE, CLAMP, SHRINK, RESIZE_HEIGHT
};
struct LabelLayoutInfo {
float lineHeight = 0.0f;
float outlineSize = 0.0f;
float spaceX = 0.0f;
float width = 0.0f;
float height = 0.0f;
float anchorX = 0.5f;
float anchorY = 0.5f;
float shadowX = 0.0f;
float shadowY = 0.0f;
int32_t shadowBlur = -1;
Color4B shadowColor;
Color4B color;
Color4B outlineColor;
bool wrap = false;
bool bold = false;
bool italic = false;
bool underline = false;
LabelAlignmentV valign = LabelAlignmentV::CENTER;
LabelAlignmentH halign = LabelAlignmentH::CENTER;
LabelOverflow overflow = LabelOverflow::NONE;
};
enum class PixelMode {
AI88,
A8,
RGB888,
BGRA8888,
INVAL,
};
int PixelModeSize(PixelMode mode);
class GlyphBitmap {
public:
GlyphBitmap(std::vector<uint8_t>& data, int width, int height, Rect rect, int xAdvance, PixelMode mode, int outline);
GlyphBitmap(GlyphBitmap&& other) noexcept;
int getWidth() const { return _width; }
int getHeight() const { return _height; }
Rect getRect() const { return _rect; }
int getXAdvance() const { return _xAdvance; }
int getOutline() const { return _outline; }
PixelMode getPixelMode() const { return _pixelMode; }
std::vector<uint8_t>& getData() { return _data; }
private:
int _width = 0;
int _height = 0;
int _outline = 0;
std::vector<uint8_t> _data;
Rect _rect;
int _xAdvance = 0;
PixelMode _pixelMode;
};
}
#endif