Files
dl_old/dl/render/dl_text.cpp
T

871 lines
20 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "dl_text.h"
#ifndef DL_DISABLE_RENDER
#include "math/dl_math.h"
#include "dl_factory.h"
#include "io/dl_input.h"
#include "render/dl_free_type.h"
#include "render/dl_graphics.h"
#include "system/dl_system.h"
namespace dl
{
//! 文本默认order(因为有背景,所以不能为0)
constexpr float Z_TEXT = Math::OffsetOrder(Z_DEFAULT, 0, 1);
Text::Text() :
_coor{ nullptr },
_bFormat{ false },
_ui{ false },
_fontId{ 0 },
_areaSize{},
_order{ Z_TEXT }
{
}
bool Text::SetString(std::string_view str)
{
if (_str != str)
{
_str = str;
_refresh_string();
return true;
}
return false;
}
void Text::SetFontId(size_t id)
{
_fontId = id;
_refresh_string();
}
void Text::SetFontSize(unsigned size)
{
if (_fontSize[TextParam::NORMAL] == size)
return;
_fontSize[TextParam::NORMAL] = size;
_fontSizeReal[TextParam::NORMAL] = (unsigned)(size / _coor->GetScale()[0]);
_refresh_string();
}
void Text::SetOutline(unsigned outline, const Color& color)
{
_fontSize[TextParam::OUTLINE] = outline;
_fontSizeReal[TextParam::OUTLINE] = (unsigned)(outline / _coor->GetScale()[0]);
_col[TextParam::OUTLINE] = color;
_refresh_string();
}
void Text::SetShadow(unsigned shadow, const Color& color /*= ColorDef::BLACK*/)
{
_fontSize[TextParam::SHADOW] = shadow;
_fontSizeReal[TextParam::SHADOW] = (unsigned)(shadow / _coor->GetScale()[0]);
_col[TextParam::SHADOW] = color;
_refresh_string();
}
void Text::SetBackground(bool open, const Color& col /*= ColorDef::TEXT_BG*/)
{
//此处颜色可能发生改变
if (open)
{
if (!_sprBg)
{
_sprBg = g_factory->CreateSpriteDefault(col);
_sprBg->SetCoor(_coor);
_sprBg->SetOrder(Math::OffsetOrder(_order, 0, 0, 0));
}
else
_sprBg->SetColor(col);
_update_spr_bg();
}
else
{
_sprBg.CancelRef();
}
}
std::string Text::GetString()
{
return _str;
}
Position Text::GetSize()
{
return _areaSize * _coor->GetScale();
}
bool Text::IsPickup()
{
if (_allNode.size() == 0)
return false;
if (_ui && g_factory->GetOrderPickup() < _order)
return false;
Position pos = _coor->WorldToThis(toPosition(g_input->GetCursorPosition()));
bool ret = Each::Less({}, pos)
&& Each::Less(pos, _areaSize);
return ret;
}
std::size_t Text::Pos2Index(const Position& pos)
{
for (size_t i = 0; i < _allNode.size(); ++i)
{
TextNode& node = _allNode[i];
if (!node._spr)
continue;
if (Math::IsDotInQuad(pos, node._spr->GetQuad()))
return i;
}
return -1;
}
dl::Position Text::Index2Pos(size_t i)
{
if (_allNode.empty())
return {};
if (i >= _allNode.size())
{
TextNode& node = _allNode.back();
if (node._ch == '\n')
return { 0, node._xy[1] };
else
return _allNode.back().GetRightTop();
}
return _allNode[i].GetLeftTop();
}
dl::Position Text::GetNodeSize(size_t i)
{
if (i >= _allNode.size())
return {};
TextNode& node = _allNode[i];
return Position{ node._advance, node._lineH } * _coor->GetScale();
}
unsigned Text::GetNodeFontSize(size_t i)
{
if (i >= _allNode.size())
return _fontSize[TextParam::NORMAL];
TextNode& node = _allNode[i];
return (unsigned)(node._lineH * _coor->GetScale()[1]);
}
size_t Text::Pos2IndexOutRange(const Position& pos)
{
size_t ret = -1;
// 找到距离最近的,且行距离优先
float dx_min = std::numeric_limits<float>::max();
int dy_min = std::numeric_limits<int>::max();// 最近匹配行
for (size_t i = 0; i < _allNode.size(); ++i)
{
TextNode& node = _allNode[i];
Position lt = node.GetLeftTop();
int dy = (int)(1 + (lt[1] - pos[1]) / _fontSize[TextParam::NORMAL]);
if (dy < 0)
continue;
if (dy < dy_min)
{
dy_min = dy;
dx_min = std::abs(pos[0] - lt[0]);
ret = i;
}
else if(dy == dy_min)
{
Float dx = std::abs(pos[0] - lt[0]);
if (dx < dx_min)
{
dx_min = dx;
ret = i;
}
}
}
return ret;
}
size_t Text::Pos2CursorIndex(const Position& pos)
{
size_t ret = Pos2Index(pos);
if (ret == -1)
{
ret = Pos2IndexOutRange(pos);
}
// 中间偏移
if (ret != -1 && _allNode[ret]._ch != '\n')
{
TextNode& node = _allNode[ret];
Quad quad = node._spr->GetQuad();
Position center = quad.Sum(0, 2) / 2_f;
if (pos[0] > center[0])
++ret;
}
return ret;
}
void Text::RenderDebug()
{
g_gfx->SetCoor(_coor);
g_gfx->SetOrder(Math::OffsetOrder(_order, 0, 0, -1));
g_gfx->SetColor(createColor(ColorDef::GREEN, 0.2f));
Quad quad = createQuad(_areaSize, false);
g_gfx->QuadSolid2D(quad);
g_gfx->SetColor(createColor(ColorDef::RED, 1.0f));
g_gfx->Line2D(quad.Get(0), quad.Get(2));
g_gfx->Line2D(quad.Get(1), quad.Get(3));
}
Text::~Text()
{
delete _coor;
}
void Text::_update_order()
{
if (_sprBg)
_sprBg->SetOrder(Math::OffsetOrder(_order, 0, 0, 0));
for (auto& iter : _allNode)
{
if (iter._spr)
iter._spr->SetOrder(Math::OffsetOrder(_order, 0, 0, -4));
if (iter._sprOutline)
iter._sprOutline->SetOrder(Math::OffsetOrder(_order, 0, 0, -3));
if (iter._sprShadow)
iter._sprShadow->SetOrder(Math::OffsetOrder(_order, 0, 0, -2));
if (iter._sprBg)
iter._sprBg->SetOrder(Math::OffsetOrder(_order, 0, 0, -1));
}
}
void Text::_update_spr_bg()
{
_sprBg->SetQuad(createQuad(_areaSize, false));
}
void Text::_refresh_string()
{
//由于标签保留使用中文的可能性,这里要保证单个字符比较的正确性
//而且标签转换数值,所以使用utf_string
/*if(p_str_utf)
_str = CodeCvt::Utf32ToUtf8(*p_str_utf);*/
_allNode.clear();
if (_str.empty())
{
//这里需要计算出一些数据
_update_layout();
return;
}
// 空白字符也生成精灵,用于拾取定位等
auto fn_write_char = [&](size_t id, unsigned size, unsigned outline, unsigned shadow,
char32_t ch, const Color& col, const Color& col_outline, const Color& col_shadow,
const Color& col_bg = ColorDef::NONE)
{
auto fn_make_special = [&](TextNode& node, const Size& size) {
node._quad[0] = createQuad(size, false);
node._quad[0] += toPosition(0, -(float)size[1]);
gcSprite spr = g_factory->CreateSpriteDefault();
spr->SetCoor(_coor);
spr->SetColor(ColorDef::NONE);
spr->SetQuad(node._quad[0]);
node._spr = spr;
};
//特殊字符处理
if (ch == U' ')
{
TextNode node;
node._ch = ch;
node._lineH = (Float)size;
node._ascent = (Float)size;
node._advance = (Float)(size / 2u);
fn_make_special(node, Size{ size / 2u, size });
_allNode.push_back(node);
return;
}
if (ch == U'\t')
{//此处advance为几个字体宽度
TextNode node;
node._ch = ch;
node._lineH = (Float)size;
node._ascent = (Float)size;
node._advance = 2;
fn_make_special(node, Size{ size * 2u, size });
_allNode.push_back(node);
return;
}
if (ch == U'\n')
{
TextNode node;
node._ch = ch;
node._lineH = (Float)size;
node._ascent = (Float)size;
node._advance = 0;
fn_make_special(node, Size{ size, size });
_allNode.push_back(node);
return;
}
gcSprite spr, spr_outline, spr_shadow;
Float advance = g_factory->CreateSpriteChar({ id, ch, size, outline }, spr, spr_outline);
spr->SetCoor(_coor);
spr->SetColor(col);
if (outline > 0)
{
spr_outline->SetCoor(_coor);
spr_outline->SetColor(col_outline);
}
if (shadow > 0)
{
spr_shadow = outline > 0 ? g_factory->CloneSprite(spr_outline) : g_factory->CloneSprite(spr);
spr_shadow->SetColor(col_shadow);
}
const FreeType::Property& prop = g_freetype->GetProperty(id, size);
TextNode node;
node._ch = ch;
node._lineH = prop.GetLinespace();
node._ascent = prop._ascent;
node._advance = advance;
node._shadow = shadow;
node._spr = spr;
node._sprOutline = spr_outline;
node._sprShadow = spr_shadow;
gcSprite& spr_bg = node._sprBg;
if (col_bg != ColorDef::NONE)
{
spr_bg = g_factory->CreateSpriteDefault(col_bg);
spr_bg->SetQuad(createQuad(node.GetLeftTop(), node.GetRightDown()));
spr_bg->SetCoor(_coor);
}
node.RecordQuad();
_allNode.push_back(node);
};
std::u32string str_utf;
/*if (p_str_utf)
str_utf = *p_str_utf;
else*/
{
if (CodeCvt::Utf8::Check(_str.c_str()) == 0)
{
log_err0("字符串不是u8编码!");
std::string str_u8 = CodeCvt::MultiByteToUtf8(_str.c_str());
str_utf = CodeCvt::Utf8ToUtf32(str_u8);
}
else
str_utf = CodeCvt::Utf8ToUtf32(_str);
}
//
if (_bFormat)
{
enum class FlagState
{
NORMAL,
FLAG,
};
FlagState state = FlagState::NORMAL;//当前读取状态
Color cur_color = _col[TextParam::NORMAL];//当前颜色
Color cur_color_outline = _col[TextParam::OUTLINE];//当前颜色
Color cur_color_shadow = _col[TextParam::SHADOW];//当前颜色
Color cur_color_bg = ColorDef::NONE;
size_t cur_font_id = _fontId;//当前字体
unsigned cur_font_size = _fontSizeReal[TextParam::NORMAL];
unsigned cur_font_ouline = _fontSizeReal[TextParam::OUTLINE];//当前描边大小
unsigned cur_font_shadow = _fontSizeReal[TextParam::SHADOW];//当前阴影偏移
Float s = 1.0f / _coor->GetScale()[0];
//标签使用wchar_t
std::wstring str_flag;
for (auto& iter : str_utf)
{
if (iter == 0)
{
if (iter != str_utf.back())
log_warn0("空字符出现在非尾部!");
break;
}
const char32_t& ch = iter;
switch (state)
{
case FlagState::NORMAL:
if (ch == U'[')
state = FlagState::FLAG;
else
{//以当前状态写入一个字符
fn_write_char(cur_font_id,
cur_font_size, cur_font_ouline, cur_font_shadow,
ch, cur_color, cur_color_outline, cur_color_shadow,
cur_color_bg);
}
break;
case FlagState::FLAG:
if (ch ==U']')
{
//分长度处理
if (str_flag.empty())
{
log_err0("空标签");
}
else if (str_flag.size() == 1)
{
if (str_flag[0] == L'f')
{
cur_font_id = _fontId;
cur_font_size = _fontSizeReal[TextParam::NORMAL];
cur_font_ouline = _fontSizeReal[TextParam::OUTLINE];
cur_font_shadow = _fontSizeReal[TextParam::SHADOW];
}
else if (str_flag[0] == L'c')
{
cur_color = _col[TextParam::NORMAL];
cur_color_outline = _col[TextParam::OUTLINE];
cur_color_shadow = _col[TextParam::SHADOW];
}
else if (str_flag[0] == L'b')
{
cur_color_bg = ColorDef::NONE;
}
else if (str_flag[0] == L'n')
{
//以当前状态写入\n
fn_write_char(cur_font_id, cur_font_size, cur_font_ouline, cur_font_shadow, U'\n', cur_color, cur_color_outline, cur_color_shadow);
}
else if (str_flag[0] == L't')
{
//以当前状态写入\t
fn_write_char(cur_font_id, cur_font_size, cur_font_ouline, cur_font_shadow, U'\t', cur_color, cur_color_outline, cur_color_shadow);
}
else if (str_flag[0] == L'l')
{
//以当前状态写入 [
fn_write_char(cur_font_id, cur_font_size, cur_font_ouline, cur_font_shadow, U'[', cur_color, cur_color_outline, cur_color_shadow);
}
else if (str_flag[0] == L'r')
{
//以当前状态写入 ]
fn_write_char(cur_font_id, cur_font_size, cur_font_ouline, cur_font_shadow, U']', cur_color, cur_color_outline, cur_color_shadow);
}
}
else
{
if (str_flag[1] != L'_')
log_err0("未知标签:" + CodeCvt::WideCharToUtf8(str_flag));
else
{
if (str_flag[0] == L'f')
{
std::vector<std::wstring> vec;//f、id
split_no_empty(str_flag, L'_', vec);
if (vec.size() > 1)
cur_font_id = stoi(vec[1]);
}
if (str_flag[0] == L's')
{
std::vector<std::wstring> vec;//s、大小、描边、阴影
split_no_empty(str_flag, L'_', vec);
if (vec.size() > 1)
cur_font_size = (unsigned)(stoi(vec[1]) * s);
if (vec.size() > 2)
cur_font_ouline = (unsigned)(stoi(vec[2]) * s);
if (vec.size() > 3)
cur_font_shadow = (unsigned)(stoi(vec[3]) * s);
}
else if (str_flag[0] == L'c')
{
std::vector<std::wstring> vec;//c、大小、描边、阴影
split_no_empty(str_flag, L'_', vec);
if (vec.size() > 1)
cur_color = g_factory->GetColor(CodeCvt::WideCharToUtf8(vec[1].c_str()));
if (vec.size() > 2)
cur_color_outline = g_factory->GetColor(CodeCvt::WideCharToUtf8(vec[2].c_str()));
if (vec.size() > 3)
cur_color_shadow = g_factory->GetColor(CodeCvt::WideCharToUtf8(vec[3].c_str()));
}
else if (str_flag[0] == L'n')
{
std::vector<std::wstring> vec;//n、次数
split_no_empty(str_flag, L'_', vec);
//以当前状态写入\n 多次
if (vec.size() > 1)
{
size_t i = stoi(vec[1]);
for (size_t j = 0; j < i; ++j)
fn_write_char(cur_font_id, cur_font_size,
cur_font_ouline, cur_font_shadow, U'\n',
cur_color, cur_color_outline, cur_color_shadow);
}
}
else if (str_flag[0] == L't')
{
std::vector<std::wstring> vec;//n、宽度
split_no_empty(str_flag, L'_', vec);
//指定宽度 写入\t
TextNode node;
node._ch = U'\t';
node._advance = (Float)(stoi(vec[1]));
node._lineH = g_freetype->GetProperty(cur_font_id, cur_font_size).GetLinespace();
_allNode.push_back(node);
}
else if (str_flag[0] == L'b')
{
std::vector<std::wstring> vec;//b、背景颜色
split_no_empty(str_flag, L'_', vec);
if (vec.size() > 1)
cur_color_bg = g_factory->GetColor(CodeCvt::WideCharToUtf8(vec[1].c_str()));
}
}
}
state = FlagState::NORMAL;
str_flag.clear();
}
else
{
assert(ch < 65536);//TODO utf_char转wchar_t
str_flag.push_back((wchar_t)ch);
}
break;
default:
log_err0("错误的标签!");
break;
}
}
}
else
{
Float sx = 0;
for (auto& iter : str_utf)
{
if (iter == 0)
{
if (iter != str_utf.back())
log_warn0("空字符出现在非尾部!");
break;;
}
const char32_t& ch = iter;
fn_write_char(_fontId,
_fontSizeReal[TextParam::NORMAL], _fontSizeReal[TextParam::OUTLINE], _fontSizeReal[TextParam::SHADOW], ch,
_col[TextParam::NORMAL], _col[TextParam::OUTLINE], _col[TextParam::SHADOW]);
}
}
_update_order();
_update_layout();
}
void Text::_update_layout()
{
// 第一遍处理,生成每行
struct Line
{
size_t _beg;// 前闭后开
size_t _end;
Float _w;// 宽度
Float _lineH;// 行间距
Float _ascent;// 基线上方高度
};
std::vector<Line> vec_line;
size_t j = 0;//单行下标
size_t beg = 0;
Float sx = 0;
Float max_h = (Float)_fontSizeReal[TextParam::NORMAL];
Float max_base = 0;
for (size_t cur = 0; cur < _allNode.size(); ++cur)
{
auto& node = _allNode[cur];
// update_layout会多次调用,注意不能随便改变node的内容
if (node._ch == '\n')
{
// 换行符在当前行
vec_line.push_back({ beg, cur + 1, sx, max_h, max_base });
beg = cur + 1;
sx = 0;
max_h = (Float)_fontSizeReal[TextParam::NORMAL];
max_base = 0;
}
else if (node._ch == '\t')
{
unsigned advance = (unsigned)(_fontSizeReal[TextParam::NORMAL] * node._advance);
Float x = (Float)Math::Align((unsigned)sx, advance);
//对齐后任在原地,则步进advance
if (x == sx)
{
node._advance1 = (Float)advance;
sx += advance;
}
else
{
node._advance1 = x - sx;
sx = x;
}
}
else
{
if (_layout._maxW)
{//自动换行
Float s = 1.0f / _coor->GetScale()[0];
Float max_w = _layout._maxW * s;
if (sx + node._advance * 2.0f > max_w)
{
//如果开启行首不能为特定符号
if (_layout._isCantFirstPunct
&& j != 0//不是首行
)
//TODO:由于编码问题,这里暂时未处理
//&& !_is_char_can_first(node._ch))//不能在行首
{
sx += node._advance;
if (node._lineH > max_h)
max_h = node._lineH;
}
else
{
//如果开启单词必须在单行
if (_layout._isWordInSingleLine
&& isalpha(node._ch))
{//往前回退到非字母
for (size_t i = cur; i > beg; --i)
{
if (!isalpha(_allNode[i]._ch))
{
vec_line.push_back({ beg, i, sx, max_h, max_base });
beg = i;
sx = 0;
max_h = (Float)_fontSizeReal[TextParam::NORMAL];
max_base = 0;
break;
}
else//每回退一个减去对应宽度
sx -= _allNode[i]._advance;
}
}
else
{//正常自动换行
sx += node._advance;
// 下次循环cur会加1
vec_line.push_back({ beg, cur + 1, sx, max_h, max_base });
beg = cur + 1;
sx = 0;
max_h = (Float)_fontSizeReal[TextParam::NORMAL];
max_base = 0;
}
}
}
else
{
sx += node._advance;
if (node._ascent > max_base)
max_base = node._ascent;
if (node._lineH > max_h)
max_h = node._lineH;
}
}
else
{
sx += node._advance;
if (node._ascent > max_base)
max_base = node._ascent;
if (node._lineH > max_h)
max_h = node._lineH;
}
}
}
if (beg != _allNode.size())
{//尾行
vec_line.push_back({ beg, _allNode.size(), sx, max_h, max_base });
}
//计算max_w
Float max_w = 0;
for (auto& iter : vec_line)
{
if (iter._w > max_w)
max_w = iter._w;
//dnd_debug(DL::TEST, format("[{}, {}) - {:.0f},{:.0f}", iter._beg, iter._end, iter._w, iter._h));
}
//逐行设定位置
Float sy = 0;
for (auto& iter : vec_line)
{
Float x = 0;
if (_layout._alignHorizontalLine == Align::Direction::FRONT)
x = max_w - iter._w;
else if (_layout._alignHorizontalLine == Align::Direction::ZERO)
x = (max_w - iter._w) / 2;
Float sx = 0;
for (size_t i = iter._beg; i < iter._end; ++i)
{
auto& node = _allNode[i];
//垂直对齐,本行内
//底对齐,往下偏移本行最大基线大小
Float y = node._ascent;
node.Set(sx + x, sy + y);
if (node._ch == '\t')
sx += node._advance1;
else
sx += node._advance;
}
sy += (iter._lineH * _layout._lineSpacing);
}
// sy为实际整体高度
_areaSize = { max_w, sy };
if (_sprBg)
_update_spr_bg();
}
bool Text::_is_char_can_first(char ch)
{
// && ch != ','
// && ch != '。'
// && ch != '!'
// && ch != '?'
return !ispunct(ch)
|| (ch != ','
&& ch != '.'
&& ch != '!'
&& ch != '?');
}
bool Text::_update_scale()
{
// 计算缩放
Float s = 1.0f;
Coor* coor = _coor->GetParent();
while (coor)
{
s *= coor->GetScale()[0];
coor = coor->GetParent();
}
assert(s);
Float s_cur = _coor->GetScale()[0];
Float s_inv = 1.0f / s;
if (std::abs(s_inv - s_cur) > 0.01f)
{
_fontSizeReal[TextParam::NORMAL] = (unsigned)(_fontSize[TextParam::NORMAL] * s);
_fontSizeReal[TextParam::OUTLINE] = (unsigned)(_fontSize[TextParam::OUTLINE] * s);
_fontSizeReal[TextParam::SHADOW] = (unsigned)(_fontSize[TextParam::SHADOW] * s);
_coor->SetScale(s_inv);
return true;
}
return false;
}
void Text::Render()
{
if (_update_scale())
{
_refresh_string();
}
if (_ui && IsPickup())
{
if (g_system->GetStep() != SystemStep::RENDER)
{
log_err0("请在Render函数调用,否则无法正确处理拾取穿透!");
}
g_factory->SetOrderPickup(_order);
g_factory->_add_pickup_text(this);
}
if (_sprBg)
_sprBg->Render();
for (auto& iter : _allNode)
{
if (iter._sprShadow)
iter._sprShadow->Render();
if (iter._sprOutline)
iter._sprOutline->Render();
if (iter._spr)
iter._spr->Render();
if (iter._sprBg)
iter._sprBg->Render();
}
}
void Text::SetCenter(const Position& pos)
{
SetPositionAlign(pos, { Align::Direction::ZERO, Align::Direction::ZERO });
}
void Text::SetOrder(float order)
{
if (_order != order)
{
_order = order;
_update_order();
}
}
void Text::SetColor(const Color& col, TextParam type)
{
if (_col[type] != col)
{
_col[type] = col;
_refresh_string();
}
}
}
#endif